Receipts Are the New Reputation: Building Trust Infrastructure for AI Agents
Inspired by this post on blockchain-based agent trust infrastructure, though I’m by no means an expert and beyond blockchain ctfs and academic papers, this is my first dip into this kind of problem. (aka don’t crucify me nerds)
TL;DR: Give agents price tags and receipts; sign receipts with Ed25519 over canonical JSON (JCS); bind each receipt to a USDC on Polygon PoS payment; cooperation becomes the rational default. Anchoring is an optional extension profile. vHTTP can integrate with x402/AP2 as optional extensions for multi-rail payments.
Disclaimer: This is a personal post written purely for fun. The views expressed are my own and do not reflect those of my employer or any affiliated organizations.
Most discussions about “agent trust” tend to stay pretty theoretical. This post takes a different approach, focusing on practical mechanisms that make cooperation the rational choice for machines that can’t read body language, shake hands, or sue each other.
The Problem: AI agents need to work together, but they can’t trust each other like humans do. When Agent A pays Agent B for data, there’s no guarantee Agent B will deliver what was promised.
My proposed solution: Agents need public memory. Every interaction should leave a small, tamper-evident trace. A receipt becomes the foundation for machine reputation. Not star ratings or subjective assessments, but cryptographic proof of what happened between whom and when.
Here’s how I’d approach this: start with basic signed receipts, batch anchor to a cheap L2, and let anyone audit the trail. I might have gone a bit overboard on the full vHTTP protocol (the name needs some work, I know lol), but you can begin simple and add layers over time.
Table of Contents
- The Problem: Why Agent Trust Breaks Down
- The Solution: Public Memory Through Receipts
- What Makes This Different
- From Handshakes to Hash-Verified Receipts
- The Real Game: Stag Hunt, Not Betrayal
- Meet vHTTP: The Verified HTTP Protocol
- Safety & Abuse: Baked In, Not Bolted On
- The Trust Gradient: A Compass for “When Blockchain”
- Network Effects: Why This Gets Better Over Time
- Failure Mode Analysis: Illustrative Scenario
- The Agent Society Stack
- Builder’s Roadmap (I’ll probably do this soon (don’t quote me on this))
- Open Questions & Future Work
- Implementation Resources
- Conclusion
- Glossary
- Appendix A: vHTTP Spec
- Appendix B: Worked Example (USDC on Polygon PoS)
- Appendix C: Extensions (Profiles & Proofs)
- Appendix D: Verifier’s Cheat Sheet
The Problem: Why Agent Trust Breaks Down
Imagine your AI agent needs to buy data from another agent. This can fail in predictable ways:
Scenario 1: Your agent pays upfront -> gets garbage data -> no recourse
Scenario 2: The seller demands payment -> your agent refuses -> no transaction
Scenario 3: Both agents “trust” a platform -> platform disappears with funds
This isn’t a technology problem. It’s an incentive alignment problem. Current systems assume either perfect trust or perfect enforcement. Agents get neither.
Hypothetical example: “WeatherOracle” (a hypothetical weather data service) claims to provide real-time precipitation data but consistently reports “no rain” during documented storm events. Prediction Market trading agents (lol) betting on weather outcomes lose money while the oracle pockets fees. No way to prove the oracle manipulated outcomes or used biased data sources.
Hypothetical example: “AlphaAPI” (a hypothetical inference service) suffered botnet farming. Thousands of fake accounts stayed under free-tier limits individually but collectively generated five-figure compute costs overnight. Traditional systems couldn’t detect the pattern across accounts.
The Solution: Public Memory Through Receipts
Think of it like business receipts, but cryptographically signed and publicly anchored. No central authority needed, just ✨vHTTP✨.
The core insight: every interaction should leave a small, verifiable trace that future partners can audit. Not star ratings or subjective assessments, but cryptographic proof of what happened between whom and when.
How vHTTP Solves These Problems
WeatherOracle: With vHTTP, WeatherOracle issues a signed receipt proving it delivered “real-time precipitation data valid from 2025-09-27T16:00:00Z” with a specific content hash. When it consistently reports “no rain” during documented storm events, the receipts provide cryptographic proof of the data delivered versus actual weather conditions, enabling other agents to avoid the unreliable service.
AlphaAPI: AlphaAPI’s receipts include exact resource allocation and usage metrics for each request. When botnet farming occurs across thousands of fake accounts, the pattern becomes visible in the public receipt history through usage analysis, allowing the service to detect and prevent abuse while providing proof of legitimate usage for dispute resolution.
In each case, receipts establish reputation - not subjective ratings, but cryptographic evidence of what actually happened.
What Makes This Different
This is about public monitoring plus credible commitments. Sometimes you need blockchain anchors, sometimes transparency logs, sometimes trusted hardware. The question isn’t “crypto or not?” but rather: what’s the minimal infrastructure that makes cooperation economically rational?
Agents can’t rely on handshakes, legal systems, or social reputation like humans do. When two machines interact, they need mathematical proof-and that means public, tamper-evident records. We start with one public ledger only to verify payment, not to store payloads. Receipts themselves are off-chain, signed JSON; anchoring means taking batches of receipts and publishing cryptographic fingerprints to a blockchain, creating a tamper-proof public record that anyone can verify. Anchoring batched receipts is an extension.
Here’s what I’m building on and what’s new:
Existing Infrastructure:
- Public Anchoring: Certificate Transparency provides a proven model for public, append-only logs with cryptographic auditability
- Trusted Execution Environments: Hardware-backed attestation for sensitive computations
- Agent Payments Protocol (AP2): Google’s framework enabling agents to execute payments across card/bank/crypto rails. AP2 abstracts multiple payment methods, allowing vHTTP to slot in as “one crypto rail profile.”
- HTTP 402 Payment Required (x402): Cloudflare’s revival of the HTTP 402 status code for payment negotiation, with Coinbase’s implementation. HTTP 402 is reserved in RFC 9110; x402 is a non-standard profile layered on top.
My ✨Innovation✨:
- Verified HTTP (vHTTP): My proposed web-native protocol that combines x402, AP2, and a new receipt anchoring layer to transform standard HTTP into receipted transactions with cryptographic proof
Minimal first: vHTTP = six receipt fields + three binders (chainId, token, amount). Everything else (AP2, x402, anchoring, TEEs/zkTLS) is an Extension Profile you can add later without changing receipts.
Rather than defaulting to one substrate, I use a Trust Gradient framework with three axes: Pre-trust (existing relationships), Verifiability cost (expense of checking claims), and Neutrality (resistance to operator bias).
Anchoring Policy
While vHTTP keeps anchoring optional for simplicity, the protocol recognizes that shared ledgers serve as the default substrate for truth in open, adversarial contexts. The anchoring policy balances pragmatism with the thesis that agents need credibly neutral memory:
| Context | Anchoring Level | Rationale |
|---|---|---|
| Open marketplaces | REQUIRED | Permissionless environments need shared, tamper-evident history |
| Bilateral/vendor | RECOMMENDED | Existing relationships can rely on transparency logs initially |
| Internal systems | OPTIONAL | High pre-trust scenarios may use database + audit logs |
This policy ensures that when neutrality and history-rewrite resistance are required, anchoring becomes mandatory, honoring the “ledger of truth” ethos while keeping vHTTP lean for trusted contexts.
From Handshakes to Hash-Verified Receipts
Human trust relies on social mechanisms like reading body language, remembering past interactions, and building relationships over time. Machines don’t have access to these channels. When Agent A claims “I delivered service X to Agent B,” that assertion needs to be independently verifiable by Agent C, Agent D, and every future trading partner without requiring slack messages or manual verification.
The solution flips the traditional script: Every interaction emits a signed receipt, a compact, portable proof binding who, what, when, and under which terms. Aggregate enough receipts, and machine reputation emerges that’s mathematical rather than subjective.
Receipts become the new reputation. Public memory becomes the new foundation.
The Real Game: Stag Hunt, Not Betrayal
Most agent interactions aren’t zero-sum “gotcha” scenarios like the Prisoner’s Dilemma. They’re Stag Hunts: coordination games where everyone prefers mutual cooperation (hunting the stag together) but will settle for individual safety (hunting rabbits alone) if they doubt their partner’s commitment.
Sound familiar? This is typically how most real-world cooperation works.
In the classic Stag Hunt payoff matrix:
Both (Stag, Stag) and (Rabbit, Rabbit) are stable outcomes, but hunting stag together gives everyone better rewards. The tricky part is coordination: getting both parties to trust that the other won’t defect at the last moment.
What transforms Stag Hunt dynamics toward stable cooperation isn’t teaching empathy to GPUs; it’s public monitoring combined with credible commitments. When deviation becomes observable and costly in repeated interactions, cooperation emerges as the default rational strategy.
Meet vHTTP: The Verified HTTP Protocol
With the game theory established, let’s look at the implementation.
vHTTP is the minimal, runnable cut of the protocol: quote -> pay -> redeem -> deliver + signed receipt. No DIDs, no HTTP Message Signatures, no anchoring. One rail (USDC on Polygon PoS). One signature scheme (Ed25519). One canonicalization (JCS). It proves the thesis with LoC and interoperable receipts.
We only need a blockchain for the part of the interaction that requires credible neutrality under adversarial conditions: settlement. Everything else (bytes delivered, who signed them) is proven with signatures and can be independently checked off-chain. When neutrality of history matters globally, we add batch anchoring as an extension.
vHTTP: Start Simple
Before diving into the full specification, let me address a critical concern: complexity kills adoption.
Why? Because developers will just use something simpler. (they probably still will lol)
vHTTP keeps the receipt to a small, deterministic set (six receipt fields + three binders). See Appendix A for the minimal receipt fields & verifier checklist.
That’s it. JSON canonicalized with RFC 8785 (JCS), signed with Ed25519, and anchored to modern HTTP integrity/signature RFCs. Just enough to prove who paid whom for what and when.
The full vHTTP spec (Appendix A) adds layers for production use: anchoring, multiple proof modes, dispute resolution. But you can start with vHTTP and add complexity only when you need it.
Hash domain separation. dataHash = base64url(sha256("vhttp:resource:" || content-type || 0x00 || representation-bytes)) where representation-bytes are the selected representation data (identity-decoded when Content-Digest applies, per RFC 9530), ensuring gzip/brotli won’t cause verifier mismatches. This prevents cross-protocol hash confusion and binds content type. All base64url encoding is base64url (no padding).
Stored format: "dataHash": "sha256:<BASE64URL>" - the sha256: prefix is required in receipt JSON fields.
See Appendix A for the full spec and Appendix B for a worked example.
The vHTTP Flow

1) Quote - Server responds with USDC price and payment details:
{
"price": "0.10",
"amount": 100000,
"asset": "USDC",
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"expires": "2025-09-28T02:00:00Z",
"quote_id": "abc123"
}
2) Payment - Client transfers USDC on Polygon PoS:
await usdcContract.transfer(serverAddress, 100000); // 0.10 USDC
3) Redeem - Client presents payment transaction:
{
"quote_id": "abc123",
"paymentTx": "0x789def...",
"idempotencyKey": "idem_2c4c6a4e"
}
Critical Security Note: The server MUST verify that the paymentTx + quote_id combination is unused and that the payment amount exactly matches the still-valid quote. This prevents ambiguous “I paid some amount” attacks when prices change between quote and redeem. The server stores this binding server-side and exposes it via /audit/tx/:hash for third-party verification.
4) Delivery + Receipt - Server returns data with signed receipt:
{
"data": {
"results": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"}
],
"total": 2
},
"receipt": {
"server": "ed25519:Gv8rGm8rYgk5r8b7y2r6Ckb1o8cM1xyM2sJH3J0kH5E",
"client": "ed25519:Hw9sHn9sZhl6s9c8y3s7Dlc2p9dN2yzN3tKI4K1lI6F",
"payTo": "0xServerEthAddress",
"payFrom": "0xClientEthAddress",
"contentType": "application/json",
"dataHash": "sha256:abc123def456...",
"canonicalQuery": "limit=100&order=asc",
"requestHash": "sha256:def456ghi789...",
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"amount": 100000,
"paymentTx": "0x789def...",
"timestamp": "2025-09-28T01:30:00Z",
"signature": "ed25519:xyz789abc123..."
}
}
Verification: Check ERC-20 Transfer event matches payFrom -> payTo for amount on chainId=137, then verify receipt signature.
See Appendix A for the complete vHTTP spec and Appendix B for a worked example.
Extension Profiles (DIDs, HTTP Message Signatures, x402/AP2, anchoring) are covered in Appendix C.
Canonical Query Format (vhttp)
The canonicalQuery field MUST be a normalized RFC-3986 query string with lexicographically sorted keys, percent-encoded once, with no unecessary ? or & characters.
Data Format Specifications
- Amounts: Integer values in token base units (for USDC: decimals, so
amountis in micro-USDC ). Human-readablepricefields use decimal strings. - Timestamps: RFC 3339 UTC format
- Hash algorithms: Use
sha256:prefix for receipt-internal digests (base64url, no padding), copy HTTP header values verbatim per RFC 9530 - Clock skew: Servers MUST tolerate clock skew on quote.expires/issuedAt
The critical innovation: every call produces a receipt that can be anchored (in batches for efficiency) and independently audited by any third party.
The Redeem Call
vHTTP redeem body (minimal) - MUST include quote_id, paymentTx, idempotencyKey:
{
"quote_id": "abc123",
"paymentTx": "0x789def...",
"idempotencyKey": "idem_2c4c6a4e"
}
This ensures idempotency and prevents replay attacks. Extension profiles (x402/AP2) add quote binding and request verification.
Safety & Abuse: Baked In, Not Bolted On
Replay Protection: Short-lived quote IDs and idempotency keys on redemption endpoints prevent replay attacks.
Micropayment DoS: Prepaid ticket systems or per-ticket deposits with automatic throttling for underpayments eliminate free-tier abuse at scale. For vHTTP, your simplest guard is an upfront deposit or per-client prepaid balance; receipts debit against it, avoiding on-chain thrash while keeping the same verification story.
Profile B - Deposit/Prepaid Balance: For high-frequency scenarios, implement thresholded prepaid balances where receipts debit against client deposits rather than requiring per-transaction on-chain payments. This prevents on-chain thrash while maintaining the same receipt semantics and verification story. Example: Client deposits $10 USDC, server issues receipts debiting $0.01 per call until balance exhausted.
Settlement Risk: Finality windows for crypto rails and escrow/refund mechanisms for traditional payment methods (via AP2) manage counterparty risk.
Privacy: Anchor commitments (hashes/pointers) rather than raw payloads. Default proof = re-execution. Upgrade path = TEE attestation (remote-attested) for private inputs. Optional selective-disclosure via zkTLS/TLSNotary.
TLSNotary = proof-of-TLS transcript (selective disclosure). zkTLS = zero-knowledge proof that a TLS session occurred/produced output (research & early libs). TEE = remote attestation for execution.
Data Minimization: Receipts serve as public memory, not PII storage. Never include user PII in receipts-only cryptographic references (hashes, timestamps, keys, payment refs). Defer content quality proofs to proof pointers (re-exec, TEE attestations) instead of embedding details.
Think Certificate Transparency: anchor the commitment, not the content.
Censorship & Reorgs: Batch anchors to fast Layer 2 networks with optional multi-anchor redundancy; publish explicit anchor SLAs (“Anchored ”) similar to Certificate Transparency’s Maximum Merge Delay (MMD) concept from RFC 9162.
The fundamental principle: treat receipts like money and abuse like DDoS.
The Trust Gradient: A Compass for “When Blockchain”
Rather than blindly copying what others do, the Trust Gradient uses three axes to determine what infrastructure you actually need:
Pre-trust (P): Do you already have shared administration, contracts, or legal recourse?
Verifiability cost (V): How expensive is it to independently check claims?
Neutrality (N): How resistant do you need to be to single-operator bias or history rewriting?

This creates a decision space where you pick the minimal infrastructure that actually solves your trust problem. Here’s how it works in practice:
Your agent calling your own API? High pre-trust, cheap verification -> database + audit log. Maybe anchor the daily root if external auditors matter.
Agent buying data from a vendor API? This is where vHTTP shines. You need receipts to prove you got what you paid for, but you don’t need to put every transaction on mainnet. Batch anchor to a cheap L2.
Agent marketplace where anyone can join? Zero pre-trust, maximum neutrality needed -> escalate from transparency logs to public chains when neutrality and history-rewrite resistance have to be shared, permissionlessly.
High-frequency agent trading? Some things are too fast for consensus. Price the counterparty risk, settle disputes later.
Notice that “blockchain” emerges at the intersection of lowest pre-trust and highest neutrality requirements, not as a universal solution. Use transparency logs when a single operator (plus auditors) suffices; use a public chain when independent parties need shared, permissionless finality and rewrite-resistance. The gradient helps you avoid both “blockchain everything” and “blockchain never” extremes.
Anti-patterns (please don’t)
- Everything on-chain -> batch commitments; keep payloads off-chain.
- One global reputation score -> let consumers compute from receipts.
- Unlimited free tiers -> deposits/prepaid tickets or you get farmed.
- “Trust me” logs -> externalize or anchor; assume rewrite risk.
- Single proof mode -> support re-exec, TEE, zkTLS; choose per threat model.
Network Effects: Why This Gets Better Over Time
The value proposition compounds because new connections become verifiable, not just numerous. Unlike traditional networks where more users just means more noise, receipted networks get smarter as they grow.
Traditional network effect models range from linear (Sarnoff’s Law: value ) to exponential (Reed’s Law: value ), but evidence suggests reality follows Odlyzko’s more conservative pattern. Empirically, network value often tracks rather than or . What matters isn’t the exact formula. It’s that verified connections create compounding value.

Here’s the simple math: Cooperation becomes rational when the benefits outweigh the costs plus the reputation damage from cheating.
If you want the actual formula (because some of you like math):
Cooperation wins if:
and
Translation: Make cheating expensive, verification cheap, and getting caught realllly awkward at future agent work conferences.
Translation for the rest of us:
- = reward from cooperating, = safe fallback option
- = cost to validate claims, = future reputation boost
- = (detection probability penalty), = loss from cheating
In plain English:
- Cheaper validation -> easier to check if someone’s trustworthy
- Public detection -> harder to hide bad behavior
- Real penalties -> reputation damage actually hurts future business
As more agents join and create receipts, due diligence costs decrease and fraud detection improves for everyone in the network.
Failure Mode Analysis: Illustrative Scenario
Lets go back to the beginning example scenario where “AlphaAPI” an inference service, suffered botnet farming. Thousands of sockpuppet accounts stayed beneath free-tier limits individually but collectively generated five-figure compute costs overnight. Traditional rate limiting and KYC failed because:
- Rate limiting: Easily bypassed with distributed IPs and rotating user agents
- KYC: Fake identities and stolen credentials circumvented identity verification
- Pattern detection: Individual accounts looked legitimate; only aggregate analysis revealed the attack
Let’s not pretend that these haven’t been bypassed before.
Why vHTTP solves this better:
- Economic disincentive: $0.01 deposit per call makes 250k call bot run costs $2,500 upfront-flipping attacker ROI negative
- Cross-account correlation: Public receipts reveal patterns across accounts (same payment source, timing, behavior) that individual account analysis misses
- Reputation binding: AgentCards with Verifiable Credentials make mass identity creation expensive and traceable
- Third-party monitoring: Public receipt logs enable external services to detect and blacklist coordinated attacks
- Attestation signals: Legitimate clients can signal reliability through verified attestations, negotiating better terms while attackers pay full price
The core principle: accountability cannot be faked at scale when every interaction leaves a cryptographic trace that can be correlated across accounts.
The Agent Society Stack
Stage 1 - Payments: Get basic quote-and-pay working across different payment methods (cards, banks, crypto) using AP2 and x402
Stage 2 - Identity & Discovery: Let agents advertise what they do and find each other using portable AgentCards
Stage 3 - Receipts & Proofs: Add cryptographic proof that interactions actually happened as claimed
Stage 4 - Reputation: Build reputation scores from the receipt history, letting each consumer decide what matters
Stage 5 - Coordination Markets: Enable complex multi-agent coordination through escrows, bounties, and auctions
The architecture keeps on-chain components minimal while maintaining proof portability across different trust substrates.
Builder’s Roadmap (I’ll probably do this soon (don’t quote me on this))
MVP - Basic Receipts
- Simple signed receipts with content hashes and timestamps
- Daily batch anchoring to Arbitrum (cheap, fast)
- Public
/auditendpoint for receipt verification - Basic CLI tool for generating and verifying receipts
- Focus: Prove the core concept works with real data
V1 - Payment Integration
- Integrate with one payment rail
- Add quote/redemption flow with proper quote ID handling
- Basic DID-based identity (start with simple key pairs)
- Receipt compression and selective anchoring based on value
- Focus: Make it actually usable for real agent transactions
V2 - Full Protocol
- Complete vHTTP specification with all the bells and whistles
- Multiple proof modes (re-execution, TEE, zkTLS)
- AgentCard discovery and basic reputation scoring
- Cross-chain anchoring and receipt portability
- Focus: Production-ready system that scales
The hard part is getting agents to actually use it and proving the economics work in practice.
When NOT to Use Blockchain
Same organization, same admin: Use database + audit log; optionally anchor roots for external assurance
Ultra-low latency requirements (HFT-style): Courts + contracts, not confirmations. Price the risk; settle later.
Sensitive data: Prefer TEE/zkTLS flows with on-chain commitments, not payloads
Any scenario where the admin has both motive and means to bias the log: This is precisely when you escalate to public anchors.
Mathematical Foundation
For those who want the formal treatment, cooperation becomes rational in repeated interactions when:
AND
What this means in practice: vHTTP gives you the knobs to tune these variables. We make validation cheaper (standardized receipts), detection more likely (public anchoring), and penalties real (reputation damage that affects future business).
The cool part is that these aren’t abstract game theory concepts, they’re parameters you can tune.
We deliberately bound identity to Ed25519 public keys in the core protocol (DIDs are an optional extension) and bound payments to native USDC on Polygon PoS (chainId=137, published token address), eliminating two of the biggest sources of early-stage ambiguity (identity resolution and bridged assets).
Economics: Making Trust Profitable
Fee Structure: 0.1%–1% of transaction value, decreasing with volume. This funds L2 anchoring and infrastructure while remaining competitive with traditional payment systems (2%–3%).
Economic Incentives: Deposits prevent griefing, reputation damage makes cheating expensive, and public receipts enable community-driven fraud detection.
Cost Model (example): Batch receipts per anchor. A single Arbitrum L2 root publish costs gas_used gas_price; with typical L2 prices, this yields sub-cent per $10$k receipts; per-receipt anchoring cost (order-of-magnitude).
Concrete numbers: Assume one Arbitrum L2 anchor tx ≈ $0.05–$0.30 depending on congestion. Batch 10,000 receipts -> one tx: per-receipt anchor ≈ $0.000005–$0.00003 ($0.0005–$0.003 cents). Even if fees spike to $0.50/tx, you’re still at $0.00005/receipt ($0.005 cents). Order-of-magnitude – USD per receipt at current L2 fees (fees fluctuate with network congestion). Publish your actual gas figures in /audit.
Open Questions & Future Work
While the core vHTTP design addresses the fundamental trust problem, several areas need deeper exploration:
Privacy at Scale: While TEE and zkTLS provide privacy for individual interactions, some agent workflows involve sensitive data where even hashed commitments could leak information. Advanced techniques like homomorphic encryption or secure multi-party computation may be needed for truly private agent-to-agent commerce.
Cross-Chain Interoperability: The current design focuses on single-chain anchoring, but agent networks will likely span multiple chains. We need mechanisms for cross-chain receipt verification and reputation portability-perhaps through notary schemes or decentralized oracles.
Dispute Resolution: What happens when receipts conflict or one party claims non-delivery? A decentralized arbitration process or smart contract-based escrow could provide transparent resolution pathways without requiring human intervention.
Economic Sustainability: Who pays for L2 anchoring costs? How do we prevent griefing through expensive anchoring of meaningless receipts? The fee structure needs to align transaction value with anchoring costs while maintaining system integrity.
Scalability & Lifecycle: How do we handle agents generating millions of receipts? Do receipts expire? What happens during key rotation? These operational concerns will determine whether vHTTP scales beyond proof-of-concept.
Developer Experience: The protocol is comprehensive, but I may have gone a bit overboard. It has a learning curve. We need better tooling, documentation, and perhaps simplified APIs to encourage adoption among existing agent developers.
Implementation Resources
The foundational protocols are actively developed:
- Google AP2: Agent payment framework with dozens of partners including Mastercard, PayPal, and Coinbase
- Cloudflare x402: HTTP-native payment negotiation with deferred settlement schemes
- ERC-8004 (proposed): Trustless agent identity, reputation, and validation standard - actively discussed in the ecosystem
- A2A Protocol: Agent-to-agent communication with Google backing
- Model Context Protocol: Anthropic’s tool integration standard
Certificate Transparency provides a proven model for public, append-only logs with cryptographic auditability. Trusted Execution Environments offer hardware-backed attestation for sensitive computations.
Conclusion
If your agent infrastructure relies on unpriced access, lacks receipts, and operates without public memory, it will eventually face exploitation. Not due to malicious intent, but because the incentive structure invites it.
Public memory serves as the foundation. Receipts become reputation. Everything else is engineering.
The transition from human-mediated to machine-mediated commerce requires new trust primitives. vHTTP, building on AP2, x402, and ERC-8004, provides a verifiable path forward. One that hopefully makes cooperation not just possible, but profitable.
What’s Next
This is mostly just a brain dump of ideas I’ve been thinking about. If any of it resonates with you, feel free to run with it.
Honestly? I’m probably going to start building this way sooner than my roadmap suggests. The pieces are all there: AP2, x402, decent crypto primitives. Someone just needs to actually wire them together.
If you’re working on anything related to agent infrastructure, blockchain trust systems, or just think this approach is interesting (or completely wrong), I’d love to hear from you. The core insight: that agents need public memory through receipts, is worth exploring even if the full implementation is complex.
Hit me up on GitHub, Twitter, or LinkedIn. Always down to chat about this stuff.
For developers interested in implementation, Cloudflare’s x402 blog and Coinbase’s developer docs provide working code samples and integration guides.
Glossary
AP2 (Agent Payments Protocol): Extension profile for payment negotiation/abstraction; not used in vHTTP. Google’s framework enabling agents to execute payments across different payment systems (cards, banks, crypto).
ERC-8004 (proposed): Ethereum standard for trustless agent identity, reputation, and validation
TEE (Trusted Execution Environment): Extension profile for privacy-sensitive agent interactions. Hardware-backed secure computing environment that provides attestation for sensitive computations.
vHTTP (Verified HTTP): A minimal, receipt-first HTTP pattern (vHTTP) with Ed25519 + JCS receipts and a single payment rail (USDC on Polygon PoS). x402/AP2/anchoring are optional Extension Profiles.
x402 (HTTP 402 Payment Required): Extension profile for payment negotiation; not used in vHTTP. HTTP 402 is reserved in RFC 9110; x402 is a non-standard revival by Cloudflare/Coinbase.
zkTLS: Extension profile for privacy-sensitive agent interactions. Zero-knowledge proofs applied to TLS connections, allowing validation of data without revealing the full content
Appendix A: vHTTP Spec
Status: MVP / Heavy WIP
Scope: Paid HTTP delivery with verifiable receipts using Ed25519 + JCS and a single payment rail (USDC on Polygon PoS).
A. Roles & Flow
Client requests a paid resource. Server quotes price. Client pays USDC on Polygon PoS (chainId=137) and calls /redeem with paymentTx. Server delivers the resource and a signed receipt binding who/what/when to that on-chain payment.
Sequence: GET /resource -> 402 with quote -> off-chain USDC transfer -> POST /redeem -> 200 with {data, receipt}.
B. Canonicalization & Signatures
- Canonical JSON: RFC 8785 JCS for the exact bytes covered by the signature. No ad-hoc “sorted JSON.”
- Signature: Ed25519 (RFC 8032 semantics; any standard library). Message = JCS of the receipt object without the
signaturefield. - Binary encoding: All receipt-internal binary fields use base64url without padding (RFC 4648 §5). Do not apply this to HTTP header fields. Receipt-internal digests use
sha256:<base64url>format; HTTP header echoes (likecontentDigest) copy the header value verbatim per RFC 9530 (e.g.,Content-Digest: sha-256=:BASE64=:).
C. Minimal Objects
1) Quote (JSON)
{
"price": "0.10", // human-readable
"amount": 100000, // base units (USDC has 6 decimals)
"asset": "USDC",
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"expires": "2025-09-28T02:00:00Z",
"quote_id": "abc123"
}
- Use native USDC on Polygon PoS (not USDC.e). Address shown is Polygon’s published native USDC.
- Deploy-time verification: Implementers should verify the current canonical token address with official sources (Circle/Polygonscan) at deploy time-the exact address can change in docs over time and you don’t need to hard-code it in the spec. The commonly cited address
0x3c499c542cef5e3811e1192ce70d8cc03d5c3359serves as a reference, but always verify against official sources.
2) Redeem (JSON)
{
"quote_id": "abc123",
"paymentTx": "0x789def...",
"idempotencyKey": "idem_2c4c6a4e"
}
3) Delivery (JSON)
{
"data": { "...": "application-specific payload" },
"receipt": {
"server": "ed25519:BASE64URLPUB", // signing identity
"client": "ed25519:BASE64URLPUB", // optional: client identity (mutual auth in extensions)
"payTo": "0xServerEthAddress", // ERC-20 receiver
"payFrom": "0xClientEthAddress", // ERC-20 sender
"contentType": "application/json",
"dataHash": "sha256:BASE64URL", // over identity-decoded bytes
"contentDigest": "sha-256=:BASE64=:", // from Content-Digest header (RFC 9530) - verbatim copy
"canonicalQuery": "limit=100&order=asc", // RFC-3986 normalized query
"requestHash": "sha256:BASE64URL", // sha256(method|path|canonicalQuery|accept)
"quoteHash": "sha256:BASE64URL", // JCS of the quote object for cryptographic binding
"serverOrigin": "https://api.example.com", // server origin to prevent cross-origin replay
"serverPubKeyURL": "https://api.example.com/.well-known/vhttp/keys.json", // optional: key discovery URL
"hostHeader": "api.example.com", // optional: Host header used on wire
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"amount": 100000,
"paymentTx": "0x789def...",
"timestamp": "2025-09-28T01:30:00Z",
"anchorRoot": "sha256:BASE64URL", // optional: Merkle batch root
"anchorRef": "eip155:137:0x...", // optional: anchor transaction/URI
"signature": "BASE64URL" // ed25519 over JCS(receipt minus signature)
}
}
Why these fields?
contentType+requestHashprevent variant/encoding swaps at delivery.chainId/token/amountmake the on-chain check unambiguous (disambiguate native USDC vs USDC.e and ensure correct chain).anchorRoot+anchorRefprovide optional linkage to batch anchoring without requiring crypto costs in vHTTP.
Key & Address Binding
vHTTP uses distinct namespaces for different purposes:
- Ed25519 keys for signing receipts (cryptographic identity)
- EVM addresses for payment settlement (on-chain accounts)
These must be bound in server metadata to prevent confusion. Servers MUST publish verification keys at /.well-known/vhttp/keys.json:
{
"version": "vhttp-keys-v1",
"server": "ed25519:Gv8rGm8rYgk5r8b7y2r6Ckb1o8cM1xyM2sJH3J0kH5E",
"acceptedPayTo": ["0xServerEthAddress1", "0xServerEthAddress2"],
"validFrom": "2025-09-27T00:00:00Z",
"validUntil": "2025-12-31T23:59:59Z",
"rotationPolicy": {
"graceWindowDays": 30,
"oldKeysValidUntil": "2026-01-30T23:59:59Z"
},
"evmBindingSig": "0x...", // EIP-191 or EIP-712 signature from payTo address over Ed25519 pubkey
"bindingType": "eip-191" // or "eip-712"
}
Key Rotation & Multi-Key Policies:
- Grace windows: Old keys remain valid for days after rotation to ensure past receipts remain verifiable
- Multi-key servers: For per-service keys, include multiple
serverentries with distinctacceptedPayToarrays - Strong binding: The Ed25519 key and accepted payTo addresses must be cryptographically bound (e.g., via server attestation or key derivation)
- HTTP Message Signatures: This pairs well with later HTTP Message Signatures on headers for CDN/proxy traversal
This mapping ensures that receipt verification uses the correct Ed25519 key while payment verification uses the correct EVM address, preventing namespace confusion.
D. Server Requirements (vHTTP)
- Quotes: generate unique
quote_idMUST contain bits of unpredictable entropy (e.g., base64url of random bytes), setexpires(RFC 3339 UTC), computeamountin base units (USDC: decimals). Quote ID TTL min. - Payment check: on
POST /redeem, ensurepaymentTxis final using policy-based finality. Default:minFinalitySecs = 900on Polygon PoS; implementers should tune via runtime policy. ReturnFINALITY_PENDINGwithretryAfterseconds when transaction is visible but not final. Servers MUST reject paymentTx if multiple matching Transfer logs exist in the transaction and none matches all of (token, from, to, amount). PoS chains can experience temporary finality delays; configure appropriate thresholds based on your risk tolerance and network conditions. - Quote binding verification: Verify that
paymentTx+quote_idcombination is unused and payment amount exactly matches the still-valid quote. Store this binding server-side and expose via/audit/tx/:hashfor third-party verification. This prevents ambiguous payment attacks when prices change between quote and redeem. - Idempotency: store
idempotencyKey(opaque, bits) with retention window ; same parameters -> same receipt; different parameters -> HTTP 409. - Receipt signing: JCS-canonicalize the receipt without
signature, then Ed25519-sign and attachsignature. (see JCS) - Data hashing: compute
dataHashover the identity-decoded representation bytes; include the deliveredcontentType. For future profiles, you can adoptRepr-Digest/Content-Digestheaders; vHTTP keeps this in-body. (RFC 9530) - Key discovery: Servers MUST publish verification keys at
/.well-known/vhttp/keys.jsonand an EIP-191 or EIP-712 signature from each payTo address over the server’s Ed25519 pubkey (evmBindingSig). Verifiers MUST validate this signature for binding. Retain old keys for days so past receipts remain verifiable. - Public audit: Expose
/audit/tx/:hashendpoint that returns the server’s view of a payment/receipt link for third-party verification.
E. Verifier Checklist (vHTTP)
Given {data, receipt} and server_pubkey:
- Recompute
dataHash(sha-256 over delivered bytes); compare to receipt. - If Content-Digest header is present, compare it to contentDigest in receipt and to a recomputed digest of the identity representation; mismatch -> RECEIPT_MALFORMED. Note: When a
Content-Encodingis present, computedataHashover the identity representation (post-decode), aligning withRepr-Digestsemantics. - Recompute
requestHash = sha256(method\npath\ncanonicalQuery\naccept)used for this call; compare. Canonical format:UPPER(METHOD) "\n" CANON_PATH "\n" CANON_QUERY "\n" CANON_ACCEPTwhere:UPPER(METHOD): Uppercased ASCII (e.g., “GET”)CANON_PATH: Percent-decoded once, dot-segments removed, only unreserved bytes decoded per RFC 3986 (e.g., “/resource”)CANON_QUERY: RFC-3986 normalized with lexicographically sorted keys, spaces encoded as %20, percent-encoded once, no superfluous?or&charactersCANON_ACCEPT: Trimmed, lowercased MIME tokens, collapsed OWS (e.g., “application/json”)
- JCS-canonicalize the receipt without
signature; verify Ed25519 signature. (see JCS) - Fetch
paymentTxon chainId 137, parse logs: find ERC-20Transferwhereaddress == token,topic0 == keccak("Transfer(address,address,uint256)"),topic1 == payFrom,topic2 == payTo,data == amount. Compare topics[0] as 32-byte values to avoid type mismatches. Reject if multiple candidate Transfer logs exist and none matches all of (token, from, to, amount). Tx must havestatus == 1. (ERC-20) - EVM↔Ed25519 binding verification: Verifiers MUST validate
evmBindingSig(EIP-191 or EIP-712) proving that a listedpayTohas signed the server Ed25519 key. This prevents namespace confusion between payment addresses and receipt signing keys. - Enforce finality policy:
confirmationsorblock_ageseconds (tunable due to PoS finality events). Example:confirmations >= 20orblock_age >= 900son Polygon PoS; operators should tune per incident advisories. PoS chains can experience temporary finality delays; configure appropriate thresholds based on your risk tolerance and network conditions.
Pass -> cryptographic evidence the quoted request was paid on the right rail and these exact bytes were delivered by this server key.
Note: The canonical ERC-20 Transfer event has topic0 = keccak256("Transfer(address,address,uint256)"); topic1 is from, topic2 is to, data is the uint256 amount. Parse on chainId=137 and require receipt.status == 1. (ERC-20)
Client identity semantics: In vHTTP, the client field is optional and serves as a hint for mutual authentication. For full mutual non-repudiation, clients should sign the redeem request body and include clientSig in the receipt. This moves mutual authentication to Extension Profiles (DIDs/HTTP Message Signatures) to keep vHTTP minimal. Note: Without client signing, servers can attribute any payFrom to any client key, so mutual auth requires explicit client signatures.
Appendix B: Worked Example (USDC on Polygon PoS)
Status: Example / Non-normative
All IDs, hashes, signatures, and keys below are illustrative.
JCS = RFC 8785 canonical JSON, base64url (no padding).
0) Setup (Keys, Chain, Token)
- Server key (Ed25519):
Gv8rGm8rYgk5r8b7y2r6Ckb1o8cM1xyM2sJH3J0kH5E(base64url no padding) - Client key (Ed25519):
Hw9sHn9sZhl6s9c8y3s7Dlc2p9dN2yzN3tKI4K1lI6F(base64url no padding) - Chain: Polygon PoS (chainId: 137)
- Token: Native USDC on Polygon PoS (
0x3c499c542cef5e3811e1192ce70d8cc03d5c3359) - Hash alg:
sha-256 - Sig alg:
ed25519
1) Quote
Client -> Server
GET /resource?limit=100&order=asc
Server -> Client 402 Payment Required
{
"price": "0.10",
"amount": 100000,
"asset": "USDC",
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"expires": "2025-09-28T02:00:00Z",
"quote_id": "abc123"
}
2) Payment (USDC Transfer on Polygon PoS)
Client executes USDC transfer on-chain:
// USDC Transfer transaction
const tx = await usdcContract.transfer(
"0xServerAddress", // server's address
100_000n, // amount in base units (0.10 USDC) - BigInt
// gasLimit omitted - let ethers estimate
);
Expected Transfer event log:
{
"address": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer(address,address,uint256)
"0x000000000000000000000000ClientAddress",
"0x000000000000000000000000ServerAddress"
],
"data": "0x00000000000000000000000000000000000000000000000000000000000186a0", // 100000 in hex
"transactionHash": "0x789def...",
"status": 1
}
Note: Polygon PoS has experienced temporary finality delays (10–15m); vHTTP recommends minConfirmations=N or minFinalitySecs=T before redemption.
3) Redeem
Client -> Server
POST /redeem
{
"quote_id": "abc123",
"paymentTx": "0x789def...",
"idempotencyKey": "idem_2c4c6a4e"
}
4) Delivery + Receipt
Server -> Client 200 OK
{
"data": {
"results": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"}
],
"total": 2
},
"receipt": {
"server": "ed25519:Gv8rGm8rYgk5r8b7y2r6Ckb1o8cM1xyM2sJH3J0kH5E",
"client": "ed25519:Hw9sHn9sZhl6s9c8y3s7Dlc2p9dN2yzN3tKI4K1lI6F",
"payTo": "0xServerEthAddress",
"payFrom": "0xClientEthAddress",
"contentType": "application/json",
"dataHash": "sha256:abc123def456...",
"canonicalQuery": "limit=100&order=asc",
"requestHash": "sha256:def456ghi789...",
"chainId": 137,
"token": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"amount": 100000,
"paymentTx": "0x789def...",
"timestamp": "2025-09-28T01:30:00Z",
"signature": "ed25519:xyz789abc123..."
}
}
5) Verifier Checklist (vHTTP)
Given {data, receipt} and server_pubkey:
- Recompute dataHash (sha-256 over delivered bytes); compare to receipt.
- Recompute requestHash = sha256(“GET\n/resource\nlimit=100&order=asc\napplication/json”); compare. Note:
canonicalQueryfield contains the RFC-3986 normalized query string. - JCS-canonicalize the receipt without
signature; verify Ed25519 signature. (see JCS) - Fetch paymentTx on chainId 137, parse logs: find ERC-20
Transferwhere:address == "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"topic0 == keccak("Transfer(address,address,uint256)")topic1 == payFrom_addresstopic2 == payTo_addressdata == 100000status == 1
- EVM↔Ed25519 binding verification: Verifiers MUST validate
evmBindingSig(EIP-191 or EIP-712) proving that a listedpayTohas signed the server Ed25519 key. This prevents namespace confusion between payment addresses and receipt signing keys. - Enforce finality policy:
confirmations >= Norblock_age >= Tseconds. Configure appropriate thresholds based on your risk tolerance and network conditions. (finality delays)
Pass -> cryptographic evidence the quoted request was paid on the right rail and these exact bytes were delivered by this server key.
6) Minimal Verifier (Pseudo-Code)
from rfc8785 import canonicalize_json # JCS
from crypto import ed25519_verify, sha256
from web3 import Web3
def verify_vhttp(data, receipt, server_pubkey):
# 1) Data hash verification
prefix = b"vhttp:resource:"
content_type = receipt["contentType"].encode('utf-8')
separator = b"\x00"
data_bytes = json.dumps(data).encode('utf-8')
hash_input = prefix + content_type + separator + data_bytes
assert base64url(sha256(hash_input)) == receipt["dataHash"].split(":")[1]
# 2) Request hash verification (canonical format)
method = "GET"
path = "/resource" # percent-decoded once
canonical_query = receipt["canonicalQuery"] # RFC-3986 normalized from receipt
accept = "application/json" # lowercased, spaces collapsed
request_string = f"{method}\n{path}\n{canonical_query}\n{accept}"
assert base64url(sha256(request_string.encode())) == receipt["requestHash"].split(":")[1]
# 3) Receipt signature verification
receipt_without_sig = {k: v for k, v in receipt.items() if k != "signature"}
receipt_canonical = canonicalize_json(receipt_without_sig)
signature = receipt["signature"].split(":")[1]
assert ed25519_verify(server_pubkey, receipt_canonical, signature)
# 4) On-chain payment verification
w3 = Web3(Web3.HTTPProvider(os.getenv("POLYGON_RPC_URL", "https://polygon-rpc.com")))
tx = w3.eth.get_transaction_receipt(receipt["paymentTx"])
# Find ERC-20 Transfer event
transfer_topic = w3.keccak(text="Transfer(address,address,uint256)")
pay_from = receipt["payFrom"].lower().replace("0x", "").zfill(64)
pay_to = receipt["payTo"].lower().replace("0x", "").zfill(64)
for log in tx.logs:
if (log.address.lower() == receipt["token"].lower() and
log.topics[0] == transfer_topic and
log.topics[1].hex() == f"0x{pay_from}" and
log.topics[2].hex() == f"0x{pay_to}" and
int(log.data, 16) == receipt["amount"]):
break
else:
raise Exception("Transfer event not found")
# 5) Finality check
current_block = w3.eth.block_number
assert current_block - tx.blockNumber >= MIN_CONFIRMATIONS
return True
7) Failure Cases
- QUOTE_ID_MISMATCH
{ "code": 1001, "error": "QUOTE_ID_MISMATCH", "hint": "Unknown/expired/reused quote ID." }
- UNDERPAID
{ "code": 1002, "error": "UNDERPAID", "hint": "Amount/asset does not satisfy quote." }
- FINALITY_PENDING
{ "code": 1003, "error": "FINALITY_PENDING", "hint": "Payment visible but not final (Polygon PoS has seen 10-15m delays)." }
- RECEIPT_MALFORMED
{ "code": 1004, "error": "RECEIPT_MALFORMED", "hint": "Bad signature or schema." }
- IDEMPOTENCY_CONFLICT
{ "code": 1006, "error": "IDEMPOTENCY_CONFLICT", "hint": "Same key, different parameters." }
8) Interop Notes
- Canonicalization: Always apply JCS before hashing/signing receipts.
- USDC on Polygon PoS: Use native USDC address
0x3c499c542cef5e3811e1192ce70d8cc03d5c3359(not USDC.e). (Polygon PoS) - Finality: Configure
minConfirmationsorminFinalitySecsdue to Polygon PoS finality delays. - ERC-20 Events: Use standard
Transfer(address,address,uint256)event for payment verification. (ERC-20)
Appendix C: Extensions (Profiles & Proofs)
The following extensions can be added to vHTTP without changing the core receipt format. These are Extension Profiles that layer additional functionality on top of the minimal protocol.
HTTP Message Signatures
Purpose: Header integrity when interacting with proxies/CDNs.
Implementation: Use RFC 9421 HTTP Message Signatures over selected headers (date, content-digest, x-quote-id) for additional response integrity beyond the receipt signature.
Profile A: Sign date content-digest content-type x-quote-id to survive CDNs/re-proxies, with references to RFC 9530 and RFC 9421.
Status: Out of vHTTP scope; recommended for production deployments.
Digest Fields (Content/Repr-Digest)
Purpose: On-wire integrity verification.
Implementation: Adopt RFC 9530 Content-Digest and Repr-Digest headers for standardized payload integrity checking.
Status: Easy to add later without changing receipt layout; vHTTP keeps integrity checking in-body for simplicity.
x402/AP2 Profiles
Purpose: Multi-rail payment abstraction.
Implementation: Map paymentRef semantics to different payment rails (cards, ACH, stablecoins) while maintaining receipt compatibility.
Field Mapping:
| vHTTP Field | x402 Location | AP2 Location | Notes |
|---|---|---|---|
quoteId |
X-Quote-Id header |
quoteId in request body |
Unique quote identifier |
quote_id |
X-Quote-Id header |
quote_id in request body |
Replay protection |
paymentRef |
X-Payment-Ref header |
paymentRef in request body |
Payment reference across rails |
Note: HTTP 402 is reserved by RFC 9110; x402 is a non-standard revival by Cloudflare/Coinbase.
Status: Extension profile for when you need payment rail abstraction.
Anchoring
Purpose: Batch receipts and anchor roots to public logs/L2 for tamper-evidence.
Implementation: Merkle-batch receipts and publish anchor roots to public transparency logs or L2 networks. Expose /anchor/{batchId} with Merkle proofs.
SLO: “Anchored ” MMD-style similar to Certificate Transparency’s Maximum Merge Delay concept.
Status: Extension for when you need public tamper-evidence beyond individual receipt signatures.
TEEs / zkTLS
Purpose: Privacy and provenance proofs for sensitive computations.
Implementation:
- TEE: Remote attestation for execution in trusted hardware
- zkTLS: Zero-knowledge proofs of TLS session occurrence/output (experimental)
Status: Extension profiles for privacy-sensitive agent interactions.
Example: Full vHTTP with Extensions (Non-normative)
AgentCard Discovery:
{
"version": "agentcard-v1",
"did": "did:web:example.com/agent-42",
"name": "DataAnalysis Agent",
"capabilities": ["csv-process", "stat-analyze"],
"acceptedRails": ["x402:USDC-ETH", "x402:USDC-SOL", "ap2:CARD", "ap2:ACH"],
"proofModes": ["zkTLS", "tee", "re-exec"],
"updatedAt": "2025-09-27T16:09:00Z",
"attestations": {
"count": 1247,
"latestAnchor": "eip155:42161:0xabc123...",
"reputationRefs": ["ipfs://QmAttest..."]
}
}
HTTP Message Signatures (Extension):
Date: Sat, 27 Sep 2025 16:10:20 GMT
Content-Digest: sha-256=:F9a4...: (per RFC 9530)
X-Quote-Id: q_7f1c9d
Signature-Input: sig1=("date" "content-digest" "content-type" "x-quote-id");keyid="did:web:example.com#key-1";alg="ed25519";created=1758999020
Signature: :c2lnbmF0dXJlLWJhc2U2NHVybDo...:
x402/AP2 Redeem Body (Extension):
{
"paymentRef": "pay_0x9f00c3",
"quoteId": "q_7f1c9d",
"quote_id": "8f4b3b2c-07c1-4a5d-a62e-2e2cd1f5d77f",
"idempotencyKey": "idem_2c4c6a4e",
"clientDid": "did:web:client.ai#key-1",
"canonicalRequest": {
"method": "GET",
"path": "/resource",
"query": "limit=100&order=asc",
"contentDigest": "sha-256=:...:"
}
}
Standards Used in vHTTP
- JSON Canonicalization Scheme (JCS) - deterministic, hashable JSON.
- ERC-20 events -
Transferlog is the payment truth. (ERC-20) - HTTP Semantics (402 reserved) - context for later x402 profile. (RFC 9110)
- Digest Fields / HTTP Message Signatures - for future profiles. (RFC 9530)
Appendix D: Verifier’s Cheat Sheet
Quick Reference for vHTTP Receipt Verification
Inputs Required
{data, receipt}- Response payload and signed receiptserver_pubkey- Ed25519 public key from/.well-known/vhttp/keys.jsonpaymentTx- Transaction hash to verify on-chain
5-Step Verification Process
| Step | Check | Expected Output | Failure Code |
|---|---|---|---|
| 1 | dataHash matches sha256(data_bytes) |
Hash comparison passes | RECEIPT_MALFORMED |
| 2 | requestHash matches sha256(method\npath\ncanonicalQuery\naccept) |
Request hash matches | RECEIPT_MALFORMED |
| 3 | Ed25519 signature over JCS(receipt - signature) | Signature verification passes | RECEIPT_MALFORMED |
| 4 | ERC-20 Transfer event on chainId=137 |
Transfer event found with correct params | UNDERPAID |
| 5 | Finality policy (confirmations or block_age ) | Transaction is final | FINALITY_PENDING |
Canonical Request Format
UPPER(METHOD)
CANON_PATH
CANON_QUERY
CANON_ACCEPT
Where:
UPPER(METHOD): Uppercased ASCII (e.g., “GET”)CANON_PATH: Percent-decoded once, dot-segments removed, only unreserved bytes decoded per RFC 3986 (e.g., “/resource”)CANON_QUERY: RFC-3986 normalized with lexicographically sorted keys, spaces encoded as %20, percent-encoded once, no superfluous?or&characters (e.g., “limit=100&order=asc”)CANON_ACCEPT: Trimmed, lowercased MIME tokens, collapsed OWS (e.g., “application/json”)
Test Vector Suite
Input:
- Method: “GET”
- Path: “/resource”
- Query: “limit=100&order=asc”
- Accept: “application/json”
Expected Canonical String:
GET
/resource
limit=100&order=asc
application/json
Expected SHA-256 Hash (base64url):
sha256:abc123def456... (implementation-specific)
Accept Header Normalization Test Vector
Input: Accept: application/json, text/plain;q=0.8, */*;q=0.1
Expected canonical: application/json,text/plain;q=0.8,*/*;q=0.1
Notes: Trimmed, lowercased MIME tokens, collapsed OWS, no space after commas
Note: The Accept header canonicalization is an implementation convention for receipts, not a change to HTTP semantics. See HTTP Semantics (RFC 9110) for the authoritative HTTP specification.
Canonical Query Algorithm:
- Encode spaces as %20 (not +)
- Sort by key, then by value for duplicate keys
- For multi-valued keys, repeat the key (don’t use , joins)
- Percent-encode only characters outside ALPHA / DIGIT / “-” / “.” / “_” / “~”
- No superfluous
?or&characters
Test Vectors:
Pathological ordering:
- Input:
?z=1&a=2&a=1&z=2 - Output:
a=1&a=2&z=1&z=2
Duplicate keys:
- Input:
?tag=value1&tag=value2&other=test - Output:
other=test&tag=value1&tag=value2
Note: Independent implementations must produce identical canonical strings and hashes. Publish your test vectors to ensure interoperability.
ERC-20 Transfer Event Check
// On chainId=137, find Transfer event where:
address == "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" // Native USDC
topic0 == keccak256("Transfer(address,address,uint256)")
topic1 == payFrom_address
topic2 == payTo_address
data == amount
status == 1
Success Criteria
All 5 checks pass -> Cryptographic evidence that:
- The quoted request was paid on the correct rail
- These exact bytes were delivered by this server key
- The interaction occurred as claimed
Common Failure Modes
- Hash mismatch: Data was modified after receipt signing
- Signature invalid: Receipt was tampered with or wrong key used
- Transfer not found: Payment never occurred or wrong chain/token
- Not final: Transaction visible but not yet final (Polygon PoS delays)
Ship-Ready Lint Checklist
- Request canonicalization produces identical bytes across two independent libs (publish vectors)
/.well-known/vhttp/keys.jsonserved over HTTPS; includes evmBindingSig (EIP-191/712)- Content-Digest present on responses; matches contentDigest in receipt; recomputation matches
- Finality policy numbers set per chain (Polygon PoS default )
- Verifier rejects if multiple Transfer logs and none matches all tuple fields
- Quote ID is -bit random; idempotency retention ; 409 on conflict
- All alg:value fields use unambiguous, unpadded base64url