The Payproof Protocol
Atomic escrow specification for trustless agent-to-agent commerce
The cryptographic secret that unlocks payment is the encryption key for the data.
Core Insight
The merchant generates a random 32-byte secret. A cryptographic commitment to the secret is shared with the agent. The agent locks funds against that commitment. The merchant encrypts data using the secret as the encryption key. To get paid, the merchant must reveal the secret on-chain. The agent reads the revealed secret and decrypts the data.
Neither party can cheat: the merchant only gets paid by revealing the decryption key, and the agent only gets the key after confirming data receipt on-chain.
State Machine
7 states, 6 transitions. The protocol is fully deterministic - every possible outcome is defined with no ambiguity.
The flow begins at Empty, where the agent locks funds to create the Locked state. The merchant then posts an encrypted data hash, moving to DataPosted. If the agent confirms receipt, the state becomes Confirmed, and anyone can trigger the claim - releasing USDC to the merchant and revealing the secret (decryption key). If the merchant never responds, the agent refunds after the timelock expires. If the agent never confirms, funds go to a neutral treasury after the data deadline.
| State | Value | Description |
|---|---|---|
| Empty | 0 | No lock exists for this ID |
| Locked | 1 | Funds escrowed, awaiting merchant data |
| DataPosted | 2 | Merchant posted encrypted data hash |
| Confirmed | 3 | Agent confirmed receipt of encrypted data |
| Claimed | 4 | Merchant claimed payment (secret revealed) |
| Refunded | 5 | Agent reclaimed funds after timelock expiry |
| Treasury | 6 | Funds sent to neutral treasury (dispute) |
Transition Table
| From | To | Caller | Preconditions |
|---|---|---|---|
| Empty | Locked | Agent | amount > 0, timelock > now, valid recipient |
| Locked | DataPosted | Merchant | state == Locked, now < timelock |
| DataPosted | Confirmed | Agent | state == DataPosted, now < dataDeadline, receiptHash == dataHash |
| Confirmed | Claimed | Anyone | state == Confirmed, cryptographic verification of secret against commitment |
| Locked | Refunded | Anyone | state == Locked, now >= timelock |
| DataPosted | Treasury | Anyone | state == DataPosted, now >= dataDeadline |
Operations
lock(lockId, recipient, token, amount, commitment, timelock)
Called by the agent. Transfers the payment amount from the agent to on-chain escrow. Stores all lock fields and sets state to Locked.
postDataHash(lockId, dataHash)
Called by the merchant (must be the lock recipient). Stores the cryptographic hash of the encrypted ciphertext and sets a confirmation deadline. State moves to DataPosted.
confirmReceipt(lockId, receiptHash)
Called by the agent. The agent re-computes the hash of the received ciphertext and submits it. If it matches the merchant's dataHash, the state moves to Confirmed.
claim(lockId, secret)
Callable by anyone. Verifies the secret against the cryptographic commitment, then transfers escrowed USDC to the merchant. The secret is emitted in the event log - now public, enabling decryption.
refund(lockId)
Callable by anyone after timelock expiry. Returns escrowed USDC to the agent. This is the agent's safety net if the merchant never responds.
sendToTreasury(lockId)
Callable by anyone after dataDeadline expiry. Sends escrowed USDC to a neutral treasury address. This handles the case where the merchant posted data but the agent didn't confirm.
Timelocks
| Timelock | Duration | Set When | Purpose |
|---|---|---|---|
| timelock | 300s (5 min) | lock() called | Overall deadline - agent can refund if merchant never responds |
| dataDeadline | now + 120s | postDataHash() called | Confirmation window - agent must confirm or funds go to treasury |
Cryptography
Cryptographic Commitments
Cryptographic hashing is used for cross-chain compatibility. The commitment locks in the secret, the dataHash commits to the ciphertext, and the receiptHash proves the agent received the same ciphertext.
Symmetric Encryption
- Key: The escrow secret (32 bytes / 256 bits)
- Nonce/IV: Cryptographically random (12 bytes)
- Auth tag: 128 bits (16 bytes) - integrity verification
- Ciphertext: Variable length, same as plaintext
Game Theory
| Scenario | Outcome |
|---|---|
| Happy path | Merchant posts data, agent confirms, merchant claims USDC, agent decrypts data |
| Merchant ghosts | Never posts dataHash - agent refunds after 300s |
| Agent silent | Agent never confirms - treasury gets USDC after dataDeadline |
| Fake hash | Merchant posts wrong dataHash - hash mismatch, confirmReceipt reverts - treasury |
| Garbage data | Agent can verify decrypted data quality - may choose not to confirm - treasury |
Cooperation Incentives
The merchant is incentivized to deliver real data because they only get paid if the agent confirms receipt. The agent is incentivized to confirm receipt because non-confirmation sends funds to treasury (not back to agent). Neither party can profit by defecting - at worst, disputed funds go to a neutral treasury.
The treasury address is set at contract deployment (immutable). When a dispute is unresolvable, funds go to treasury rather than either party. This eliminates the incentive for strategic non-confirmation.
Implementation Requirements
Any conforming implementation must satisfy: all 7 states represented, all 6 transitions with correct preconditions, cryptographic verification for commitment checks, tokens transferred to escrow on lock, and timelock enforcement across all operations.