|
| 1 | +--- |
| 2 | +title: Trustless Sessions (Future) |
| 3 | +description: Fraud proof architecture for trustless escrow session verification |
| 4 | +--- |
| 5 | + |
| 6 | +# Trustless Sessions |
| 7 | + |
| 8 | +<Note> |
| 9 | + This document describes a **future enhancement** to the escrow system. The current implementation uses a trusted facilitator model. Trustless verification is planned for a future release. |
| 10 | +</Note> |
| 11 | + |
| 12 | +## Current Trust Model |
| 13 | + |
| 14 | +The current escrow system has a **trusted facilitator** model: |
| 15 | + |
| 16 | +| Component | Trust Level | Notes | |
| 17 | +| --------- | ----------- | ----- | |
| 18 | +| Deposit authorization | Trustless | ERC-3009 signature verified on-chain | |
| 19 | +| Off-chain debits | Trusted | Facilitator tracks in database | |
| 20 | +| Capture amounts | Trusted | Facilitator decides what to capture | |
| 21 | +| Fund recovery | Trustless | Smart contract enforces reclaim after expiry | |
| 22 | + |
| 23 | +**Key assumption:** Users trust the facilitator to honestly report API consumption. |
| 24 | + |
| 25 | +### What's Trustless Today |
| 26 | + |
| 27 | +- **Deposits**: User signs ERC-3009 authorization - cryptographically verified on-chain |
| 28 | +- **Reclaims**: Smart contract enforces reclaim after `authorizationExpiry` - no facilitator involvement needed |
| 29 | +- **Bounded exposure**: Only deposited amount at risk, never entire wallet |
| 30 | + |
| 31 | +### What Requires Trust |
| 32 | + |
| 33 | +- **Usage tracking**: Facilitator maintains authoritative ledger of session debits |
| 34 | +- **Capture amounts**: Facilitator decides how much to capture - no cryptographic proof submitted |
| 35 | +- **Billing accuracy**: No on-chain verification that captures match actual consumption |
| 36 | + |
| 37 | +## Trustless Architecture (Planned) |
| 38 | + |
| 39 | +### Overview |
| 40 | + |
| 41 | +The trustless model replaces facilitator trust with **cryptographic verification**: |
| 42 | + |
| 43 | +``` |
| 44 | +1. SESSION CREATION (unchanged) |
| 45 | + User signs ERC-3009 (one-time) |
| 46 | + Facilitator creates session, returns sessionToken |
| 47 | +
|
| 48 | +2. SESSION USAGE (unchanged UX) |
| 49 | + Client sends session token per request |
| 50 | + Facilitator returns CUMULATIVE receipt: |
| 51 | + { sessionId, nonce, cumulativeAmount, timestamp, facilitatorSig } |
| 52 | + Client stores only latest receipt (can re-fetch if needed) |
| 53 | +
|
| 54 | +3. FACILITATOR COMMITS CAPTURE |
| 55 | + Builds merkle tree of ALL issued receipts |
| 56 | + Publishes: (merkleRoot, captureAmount) for sessionId |
| 57 | + Stakes collateral (10% of capture value) |
| 58 | + CRITICAL: Cannot forge receipts after commitment |
| 59 | +
|
| 60 | +4. CHALLENGE WINDOW (3-7 days) |
| 61 | + User disputes if captureAmount > their latest receipt's cumulative |
| 62 | + Provides their highest-nonce receipt as proof |
| 63 | + Facilitator can counter with higher-nonce receipt if user is lying |
| 64 | + BUT: Facilitator must prove receipt exists in committed merkle tree |
| 65 | + Contract compares nonces: highest nonce wins (if merkle proof valid) |
| 66 | +
|
| 67 | +5. SETTLEMENT |
| 68 | + After window: funds released to receiver |
| 69 | + Collateral returned to facilitator |
| 70 | +``` |
| 71 | + |
| 72 | +### Cumulative Receipts |
| 73 | + |
| 74 | +For each API request, the **facilitator signs a cumulative receipt** showing the running total spent. This prevents users from selectively discarding receipts. |
| 75 | + |
| 76 | +```typescript |
| 77 | +interface CumulativeReceipt { |
| 78 | + sessionId: bytes32; // Links to on-chain escrow (and original ERC-3009) |
| 79 | + nonce: uint256; // Monotonically increasing per session |
| 80 | + cumulativeAmount: uint256; // TOTAL spent so far (not just this request) |
| 81 | + timestamp: uint256; // Unix timestamp |
| 82 | + facilitatorSignature: bytes; // EIP-712 signature over all above fields |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +The `facilitatorSignature` is the key binding: |
| 87 | +- Proves facilitator acknowledged this exact (sessionId, nonce, amount) tuple |
| 88 | +- Cannot be forged by user (only facilitator has signing key) |
| 89 | +- `sessionId` links to on-chain escrow → links to original ERC-3009 authorization |
| 90 | + |
| 91 | +**Example sequence:** |
| 92 | +``` |
| 93 | +Request 1: { nonce: 1, cumulativeAmount: $0.05 } |
| 94 | +Request 2: { nonce: 2, cumulativeAmount: $0.10 } |
| 95 | +Request 3: { nonce: 3, cumulativeAmount: $0.15 } |
| 96 | +... |
| 97 | +Request 100: { nonce: 100, cumulativeAmount: $5.00 } |
| 98 | +``` |
| 99 | + |
| 100 | +**Why cumulative?** Users can only dispute using their **highest nonce receipt**. If the latest receipt shows `$5.00 cumulative`, they cannot claim less was consumed. No selective discarding possible. |
| 101 | + |
| 102 | +<Note> |
| 103 | + Receipts are returned in the `PAYMENT-RESPONSE` header. Clients only need to store their **latest receipt** as proof. |
| 104 | +</Note> |
| 105 | + |
| 106 | +### Receipt Re-Fetching |
| 107 | + |
| 108 | +If a receipt response is lost (network failure), clients can re-fetch any past receipt: |
| 109 | + |
| 110 | +``` |
| 111 | +GET /api/receipts/:sessionId/:nonce |
| 112 | +Authorization: Bearer <sessionToken> |
| 113 | +
|
| 114 | +Response: { receipt: CumulativeReceipt } |
| 115 | +``` |
| 116 | + |
| 117 | +<Warning> |
| 118 | + Clients should sync their receipts before the challenge window opens. After capture is committed, the facilitator may not be required to serve old receipts. |
| 119 | +</Warning> |
| 120 | + |
| 121 | +### Why This Prevents Attacks |
| 122 | + |
| 123 | +<AccordionGroup> |
| 124 | + <Accordion title="User claims missing receipts" icon="user-xmark"> |
| 125 | + **Attack:** "I only received 80 receipts, not 100" |
| 126 | + |
| 127 | + **Defense:** Cumulative model means only the **highest nonce receipt** matters. |
| 128 | + If user's latest receipt shows `{ nonce: 100, cumulative: $5.00 }`, the cumulative amount proves total consumption. |
| 129 | + </Accordion> |
| 130 | + |
| 131 | + <Accordion title="User discards high-value receipts" icon="trash"> |
| 132 | + **Attack:** User discards receipt showing $5.00, disputes with earlier $3.00 receipt |
| 133 | + |
| 134 | + **Defense:** Facilitator counter-disputes with the higher-nonce receipt: |
| 135 | + - User provides: `{ nonce: 60, cumulative: $3.00 }` |
| 136 | + - Facilitator counters: `{ nonce: 100, cumulative: $5.00 }` with valid signature |
| 137 | + - Contract verifies: facilitator's nonce > user's nonce → dispute rejected |
| 138 | + </Accordion> |
| 139 | + |
| 140 | + <Accordion title="Facilitator over-captures" icon="user-secret"> |
| 141 | + **Attack:** Facilitator captures $10.00 but only issued receipts for $5.00 |
| 142 | + |
| 143 | + **Defense:** User disputes with their highest receipt: |
| 144 | + - User provides: `{ nonce: 100, cumulative: $5.00 }` with valid facilitator signature |
| 145 | + - Facilitator cannot counter (never issued higher receipt) |
| 146 | + - Contract verifies: $5.00 < $10.00 captured → facilitator slashed |
| 147 | + </Accordion> |
| 148 | + |
| 149 | + <Accordion title="Facilitator doesn't return receipts" icon="ban"> |
| 150 | + **Attack:** Facilitator processes requests but doesn't return receipts |
| 151 | + |
| 152 | + **Defense:** Without a signed receipt, the charge effectively didn't happen. |
| 153 | + - User's latest receipt shows actual acknowledged consumption |
| 154 | + - Facilitator can only capture up to what they signed for |
| 155 | + - Incentivizes facilitator to always return receipts |
| 156 | + </Accordion> |
| 157 | + |
| 158 | + <Accordion title="Network drops receipts" icon="wifi"> |
| 159 | + **Attack:** Network failure causes user to miss receipts |
| 160 | + |
| 161 | + **Defense:** Receipt re-fetch endpoint allows recovery: |
| 162 | + - User calls `GET /api/receipts/:sessionId/:nonce` for any past receipt |
| 163 | + - User syncs all receipts before challenge window |
| 164 | + - User's responsibility to ensure they have receipts before disputing |
| 165 | + </Accordion> |
| 166 | + |
| 167 | + <Accordion title="Facilitator inflates receipts (LIMITATION)" icon="triangle-exclamation"> |
| 168 | + **Attack:** Facilitator adds fake receipts to merkle tree before committing |
| 169 | + |
| 170 | + **Status:** NOT FULLY PREVENTED - this is a known limitation. |
| 171 | + |
| 172 | + **Why it's hard:** Facilitator controls the merkle tree. They can add fake receipts before committing. Without per-request user acknowledgments, we cannot prove delivery. |
| 173 | + |
| 174 | + **Mitigations (not cryptographic):** |
| 175 | + - User's exposure bounded by deposit (maxAmount) |
| 176 | + - Reputation damage deters fraud |
| 177 | + - User can stop using facilitator and reclaim remaining balance |
| 178 | + - Facilitator cannot capture MORE than tree total (still enforced) |
| 179 | + </Accordion> |
| 180 | +</AccordionGroup> |
| 181 | + |
| 182 | +### Merkle Commitment |
| 183 | + |
| 184 | +Facilitator batches receipts into a merkle tree. **This is critical for preventing receipt forgery** - once committed, the facilitator cannot create fake receipts. |
| 185 | + |
| 186 | +| Property | Value | |
| 187 | +| -------- | ----- | |
| 188 | +| Leaf format | `keccak256(abi.encode(receipt))` | |
| 189 | +| Tree type | Binary merkle tree | |
| 190 | +| Commitment | `commitCapture(sessionId, merkleRoot, captureAmount)` | |
| 191 | +| Collateral | 10% of batch value | |
| 192 | +| Purpose | Prevents facilitator from forging receipts after commitment | |
| 193 | + |
| 194 | +### Challenge Window |
| 195 | + |
| 196 | +<AccordionGroup> |
| 197 | + <Accordion title="How disputes work" icon="gavel"> |
| 198 | + 1. Facilitator commits `captureAmount = $50` with collateral |
| 199 | + 2. User sees capture, checks their latest receipt: `{ nonce: 80, cumulative: $30 }` |
| 200 | + 3. User submits `disputeOverCapture(sessionId, userReceipt)` |
| 201 | + 4. Contract verifies: facilitator signature valid, $30 < $50 |
| 202 | + 5. Dispute opens - facilitator has 24-48h to counter |
| 203 | + 6. If no valid counter → `resolveDispute()` slashes facilitator, refunds user $20 |
| 204 | + |
| 205 | + **Counter-dispute flow:** |
| 206 | + - Facilitator submits `{ nonce: 100, cumulative: $50 }` receipt |
| 207 | + - Contract verifies: signature valid, nonce 100 > 80 |
| 208 | + - User's dispute rejected (they had a higher receipt) |
| 209 | + </Accordion> |
| 210 | + <Accordion title="What can be disputed" icon="shield-exclamation"> |
| 211 | + - **Over-capture**: User's highest receipt shows less than captured amount |
| 212 | + - **Invalid signature**: Facilitator signature on receipt doesn't verify |
| 213 | + </Accordion> |
| 214 | + <Accordion title="Window parameters" icon="clock"> |
| 215 | + | Parameter | Value | Rationale | |
| 216 | + | --------- | ----- | --------- | |
| 217 | + | Duration | 3-7 days | Balance security vs UX | |
| 218 | + | Collateral | 10% of batch | Economic deterrent | |
| 219 | + | Slash | 100% of disputed amount | Full user refund | |
| 220 | + </Accordion> |
| 221 | +</AccordionGroup> |
| 222 | + |
| 223 | +### Smart Contract Interface |
| 224 | + |
| 225 | +```solidity |
| 226 | +interface ITrustlessEscrow { |
| 227 | + // Facilitator commits capture with merkle root of all receipts |
| 228 | + function commitCapture( |
| 229 | + bytes32 sessionId, |
| 230 | + bytes32 merkleRoot, // Root of all issued receipts |
| 231 | + uint256 captureAmount |
| 232 | + ) external payable; // msg.value = collateral stake (10% of captureAmount) |
| 233 | +
|
| 234 | + // User disputes: "My receipt shows less than you captured" |
| 235 | + function disputeOverCapture( |
| 236 | + bytes32 sessionId, |
| 237 | + CumulativeReceipt calldata userReceipt // User's highest nonce receipt |
| 238 | + ) external; |
| 239 | + // Contract verifies: |
| 240 | + // 1. facilitatorSignature is valid |
| 241 | + // 2. userReceipt.cumulativeAmount < captureAmount |
| 242 | + // If both true → dispute opened, facilitator must counter |
| 243 | +
|
| 244 | + // Facilitator counters with higher nonce receipt + merkle proof |
| 245 | + function counterDispute( |
| 246 | + bytes32 sessionId, |
| 247 | + CumulativeReceipt calldata facilitatorReceipt, |
| 248 | + bytes32[] calldata merkleProof // Proves receipt was in committed tree |
| 249 | + ) external; |
| 250 | + // Contract verifies: |
| 251 | + // 1. facilitatorSignature is valid |
| 252 | + // 2. Receipt exists in committed merkle tree (prevents forgery!) |
| 253 | + // 3. facilitatorReceipt.nonce > userReceipt.nonce |
| 254 | + // If all true → dispute rejected (user was lying) |
| 255 | +
|
| 256 | + // After challenge window with no successful counter |
| 257 | + function resolveDispute(bytes32 sessionId) external; |
| 258 | + // Slashes facilitator, refunds user the difference |
| 259 | +
|
| 260 | + // After challenge window with no disputes |
| 261 | + function finalize(bytes32 sessionId) external; |
| 262 | + // Releases funds to receiver, returns collateral |
| 263 | +} |
| 264 | +
|
| 265 | +struct CumulativeReceipt { |
| 266 | + bytes32 sessionId; |
| 267 | + uint256 nonce; |
| 268 | + uint256 cumulativeAmount; |
| 269 | + uint256 timestamp; |
| 270 | + bytes facilitatorSignature; // EIP-712 over (sessionId, nonce, cumulativeAmount, timestamp) |
| 271 | +} |
| 272 | +``` |
| 273 | + |
| 274 | +**Verification logic:** |
| 275 | +```solidity |
| 276 | +function verifyReceipt(CumulativeReceipt calldata receipt) internal view returns (bool) { |
| 277 | + bytes32 structHash = keccak256(abi.encode( |
| 278 | + RECEIPT_TYPEHASH, |
| 279 | + receipt.sessionId, |
| 280 | + receipt.nonce, |
| 281 | + receipt.cumulativeAmount, |
| 282 | + receipt.timestamp |
| 283 | + )); |
| 284 | + bytes32 digest = _hashTypedDataV4(structHash); |
| 285 | + address signer = ECDSA.recover(digest, receipt.facilitatorSignature); |
| 286 | + return signer == facilitatorAddress; |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +### Security Properties |
| 291 | + |
| 292 | +| Property | Current | Trustless | Notes | |
| 293 | +| -------- | ------- | --------- | ----- | |
| 294 | +| Over-capture (capture > receipts) | Unverifiable | **Trustless** | User can prove with receipts | |
| 295 | +| Receipt inflation (fake receipts) | Trusted | **Trusted** | Can't prove delivery without acks | |
| 296 | +| Fraud detection | None | Challenge window | For provable over-capture | |
| 297 | +| Economic security | None | Collateral slashing | For provable fraud | |
| 298 | +| User recourse | Only reclaim | Dispute + refund | For over-capture only | |
| 299 | +| Client signing | Once | Once | Preserves UX | |
| 300 | +| Bounded exposure | maxAmount | maxAmount | Limits damage from inflation | |
| 301 | + |
| 302 | +<Warning> |
| 303 | + **Limitation:** Facilitator can inflate receipts (add fake ones to merkle tree) because we cannot prove delivery without per-request user signatures. The protection is: |
| 304 | + - Facilitator cannot capture MORE than what's in their committed tree |
| 305 | + - User's exposure is bounded by their deposit (maxAmount) |
| 306 | + - Reputation damage from fraud is a deterrent |
| 307 | +</Warning> |
| 308 | + |
| 309 | +## Trade-offs |
| 310 | + |
| 311 | +### Advantages |
| 312 | + |
| 313 | +- Users can cryptographically prove over-capture fraud |
| 314 | +- Economic incentive against fraud (collateral at risk) |
| 315 | +- Preserves "sign once" UX (no per-request client signatures) |
| 316 | +- Bounded exposure limits damage from any fraud |
| 317 | + |
| 318 | +### Disadvantages |
| 319 | + |
| 320 | +- 3-7 day finality delay (receiver waits for funds) |
| 321 | +- Client must store receipts locally (storage overhead) |
| 322 | +- Requires collateral capital from facilitator |
| 323 | +- More complex implementation |
| 324 | +- Higher gas costs (commitment transactions) |
| 325 | +- Facilitator must sign each response (compute overhead) |
| 326 | +- **Receipt inflation still trusted** - facilitator can add fake receipts to tree |
| 327 | + |
| 328 | +## Migration Path |
| 329 | + |
| 330 | +<Steps> |
| 331 | + <Step title="Phase 1: Trusted (Current)"> |
| 332 | + Current model with trusted facilitator. Faster UX, simpler implementation. Trust assumptions clearly documented. |
| 333 | + </Step> |
| 334 | + <Step title="Phase 2: Hybrid (Opt-in)"> |
| 335 | + Signed receipts as optional feature. Users who want trustless can enable. Default remains trusted for faster UX. |
| 336 | + </Step> |
| 337 | + <Step title="Phase 3: Full Trustless"> |
| 338 | + Signed receipts mandatory. Dispute contract deployed. Watchtower service for monitoring. Deprecate trusted-only mode. |
| 339 | + </Step> |
| 340 | +</Steps> |
| 341 | + |
| 342 | +## Open Questions |
| 343 | + |
| 344 | +<AccordionGroup> |
| 345 | + <Accordion title="Challenge window duration" icon="clock"> |
| 346 | + **3 days vs 7 days?** |
| 347 | + - Shorter = better UX for receivers (faster access to funds) |
| 348 | + - Longer = more time to detect and dispute fraud |
| 349 | + </Accordion> |
| 350 | + <Accordion title="Collateral source" icon="wallet"> |
| 351 | + **Facilitator-funded vs protocol treasury?** |
| 352 | + - Facilitator stake = direct incentive alignment |
| 353 | + - Treasury = requires governance, less direct incentive |
| 354 | + </Accordion> |
| 355 | + <Accordion title="Watchtower service" icon="tower-observation"> |
| 356 | + **Who monitors for fraud?** |
| 357 | + - Self-monitoring (users watch own sessions) |
| 358 | + - Decentralized watchtower network |
| 359 | + - Agentokratia-operated service |
| 360 | + </Accordion> |
| 361 | + <Accordion title="Gas sponsorship" icon="gas-pump"> |
| 362 | + **Who pays for dispute transactions?** |
| 363 | + - User pays (may disincentivize small disputes) |
| 364 | + - Protocol subsidizes (encourages fraud detection) |
| 365 | + </Accordion> |
| 366 | +</AccordionGroup> |
| 367 | + |
| 368 | +## References |
| 369 | + |
| 370 | +- [Optimistic Rollups - ethereum.org](https://ethereum.org/developers/docs/scaling/optimistic-rollups/) |
| 371 | +- [Arbitrum Fraud Proofs](https://docs.arbitrum.io/how-arbitrum-works/fraud-proofs) |
| 372 | +- [Merkle Trees in Blockchains](https://www.alchemy.com/docs/merkle-trees-in-blockchains) |
| 373 | +- [State Channels - ethereum.org](https://ethereum.org/developers/docs/scaling/state-channels/) |
0 commit comments