|
| 1 | +# Payment Channel Protocol Specification |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document specifies the state machine and protocol for Stellar-based payment channels used for private recurring subscription payments in SYNCRO. Payment channels allow multiple subscription renewals to occur off-chain, with only two on-chain transactions required: one to open the channel and one to close it. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +Payment channels use a **2-of-2 multisig escrow** between the user (payer) and the SYNCRO executor (payee). Funds are locked in the escrow account, and both parties sign off-chain balance updates for each renewal cycle. This provides privacy by minimizing the on-chain footprint of recurring payments. |
| 10 | + |
| 11 | +### Key Components |
| 12 | + |
| 13 | +- **Escrow Account**: A Stellar account requiring 2-of-2 multisig (user + executor) |
| 14 | +- **Balance Allocation**: A signed off-chain state representing the current split of funds |
| 15 | +- **Sequence Number**: Monotonically increasing counter ensuring state ordering |
| 16 | +- **Time-Lock**: Prevents premature unilateral close; enables dispute resolution |
| 17 | + |
| 18 | +## State Machine |
| 19 | + |
| 20 | +``` |
| 21 | + ┌─────────────────────────────────────────┐ |
| 22 | + │ │ |
| 23 | + ▼ │ |
| 24 | +┌──────────┐ deposit ┌──────────┐ both sign ┌──────────┐ |
| 25 | +│ │──────────────▶│ │───────────────▶│ │ |
| 26 | +│ IDLE │ │ OPEN │ │ ACTIVE │◄──┐ |
| 27 | +│ │ │ │ │ │───┘ |
| 28 | +└──────────┘ └──────────┘ └────┬─────┘ |
| 29 | + │ off-chain |
| 30 | + │ renewals |
| 31 | + ┌──────────┐ │ |
| 32 | + │ │◄───────────────────┘ |
| 33 | + │ CLOSING │ close initiated |
| 34 | + │ │ |
| 35 | + └────┬─────┘ |
| 36 | + │ |
| 37 | + ┌──────────┼──────────┐ |
| 38 | + │ │ │ |
| 39 | + ▼ ▼ ▼ |
| 40 | + ┌──────────┐ ┌──────┐ ┌─────────┐ |
| 41 | + │ DISPUTED │ │CLOSED│ │TOP-UP │ |
| 42 | + │ │ │ │ │(→ACTIVE)│ |
| 43 | + └────┬─────┘ └──────┘ └─────────┘ |
| 44 | + │ |
| 45 | + ▼ |
| 46 | + ┌──────────┐ |
| 47 | + │ CLOSED │ |
| 48 | + └──────────┘ |
| 49 | +``` |
| 50 | + |
| 51 | +### States |
| 52 | + |
| 53 | +| State | Description | |
| 54 | +|-------|-------------| |
| 55 | +| **IDLE** | No channel exists. User has not deposited funds. | |
| 56 | +| **OPEN** | Escrow account created with initial deposit. Awaiting both parties to sign the initial state. | |
| 57 | +| **ACTIVE** | Channel is operational. Off-chain balance updates occur each renewal cycle. | |
| 58 | +| **CLOSING** | Close has been initiated (cooperative or unilateral). Dispute window is active for unilateral close. | |
| 59 | +| **DISPUTED** | A counterparty has submitted a newer state during the dispute window. | |
| 60 | +| **CLOSED** | Final state settled on-chain. Funds distributed according to the latest signed state. | |
| 61 | + |
| 62 | +### State Transitions |
| 63 | + |
| 64 | +| From | To | Trigger | Preconditions | Postconditions | |
| 65 | +|------|----|---------|---------------|----------------| |
| 66 | +| IDLE | OPEN | `openChannel(deposit)` | User has sufficient balance (XLM or USDC). Deposit amount >= minimum channel capacity. | Escrow account created. Funds locked in 2-of-2 multisig. Time-lock set for channel expiry. | |
| 67 | +| OPEN | ACTIVE | `activateChannel()` | Both parties have signed the initial balance allocation (state sequence 0). | Channel ready for off-chain payments. Initial state: user balance = deposit, executor balance = 0. | |
| 68 | +| ACTIVE | ACTIVE | `updateState(newAllocation)` | Both parties sign new balance allocation. New sequence number > previous. Total allocation = total deposited. | Off-chain state updated. No on-chain transaction. | |
| 69 | +| ACTIVE | ACTIVE | `topUp(amount)` | User has sufficient balance. Channel is not expired. | On-chain deposit to escrow. New signed state reflects increased total capacity. | |
| 70 | +| ACTIVE | CLOSING | `cooperativeClose()` | Both parties agree on final state and sign the closing transaction. | Final balance allocation submitted on-chain. Funds distributed immediately. | |
| 71 | +| ACTIVE | CLOSING | `unilateralClose(latestState)` | One party submits their latest signed state on-chain. | Dispute window timer starts (T blocks). Submitted state is pending. | |
| 72 | +| CLOSING | DISPUTED | `dispute(newerState)` | Counterparty submits a state with a higher sequence number within the dispute window. | Dispute timer resets. Newer state becomes the pending state. | |
| 73 | +| CLOSING | CLOSED | Dispute window expires | No dispute submitted within T blocks. | Funds distributed per the pending state. Escrow account merged/closed. | |
| 74 | +| DISPUTED | CLOSED | Dispute window expires | No further disputes within T blocks. | Funds distributed per the latest disputed state. | |
| 75 | +| ACTIVE | CLOSING | Channel expiry reached | Time-lock has expired. | Either party can force-close with latest state. | |
| 76 | + |
| 77 | +## Protocol Flows |
| 78 | + |
| 79 | +### 1. Channel Open |
| 80 | + |
| 81 | +``` |
| 82 | +User Stellar Network Executor |
| 83 | + │ │ │ |
| 84 | + │ 1. Create escrow account │ │ |
| 85 | + │ (2-of-2 multisig) │ │ |
| 86 | + │───────────────────────────────▶│ │ |
| 87 | + │ │ │ |
| 88 | + │ 2. Deposit XLM/USDC │ │ |
| 89 | + │───────────────────────────────▶│ │ |
| 90 | + │ │ │ |
| 91 | + │ 3. Sign initial state (seq=0)│ │ |
| 92 | + │ user_balance=deposit │ │ |
| 93 | + │ executor_balance=0 │ │ |
| 94 | + │────────────────────────────────────────────────────────────────▶│ |
| 95 | + │ │ │ |
| 96 | + │ 4. Executor co-signs │ │ |
| 97 | + │◄────────────────────────────────────────────────────────────────│ |
| 98 | + │ │ │ |
| 99 | + │ Channel ACTIVE │ │ |
| 100 | + │ │ │ |
| 101 | +``` |
| 102 | + |
| 103 | +### 2. Off-Chain Renewal (State Update) |
| 104 | + |
| 105 | +``` |
| 106 | +User (Off-Chain) Executor |
| 107 | + │ │ |
| 108 | + │ 1. Renewal cycle triggered │ |
| 109 | + │ │ |
| 110 | + │ 2. Propose new state (seq=N+1) │ |
| 111 | + │ user_balance -= renewal_amount │ |
| 112 | + │ executor_balance += renewal_amount │ |
| 113 | + │───────────────────────────────────────────────────────────────▶│ |
| 114 | + │ │ |
| 115 | + │ 3. Executor validates & co-signs │ |
| 116 | + │◄───────────────────────────────────────────────────────────────│ |
| 117 | + │ │ |
| 118 | + │ Both parties store signed state locally │ |
| 119 | + │ NO on-chain transaction │ |
| 120 | + │ │ |
| 121 | +``` |
| 122 | + |
| 123 | +### 3. Cooperative Close |
| 124 | + |
| 125 | +``` |
| 126 | +User Stellar Network Executor |
| 127 | + │ │ │ |
| 128 | + │ 1. Request close │ │ |
| 129 | + │───────────────────────────────────────────────────────────────▶│ |
| 130 | + │ │ │ |
| 131 | + │ 2. Both sign final closing tx │ │ |
| 132 | + │◄───────────────────────────────────────────────────────────────│ |
| 133 | + │ │ │ |
| 134 | + │ 3. Submit closing tx on-chain │ │ |
| 135 | + │───────────────────────────────▶│ │ |
| 136 | + │ │ │ |
| 137 | + │ 4. Funds distributed: │ │ |
| 138 | + │ user_balance → User │ │ |
| 139 | + │ executor_balance → Executor │ │ |
| 140 | + │ │ │ |
| 141 | + │ Channel CLOSED │ │ |
| 142 | + │ │ │ |
| 143 | +``` |
| 144 | + |
| 145 | +### 4. Unilateral Close |
| 146 | + |
| 147 | +``` |
| 148 | +User Stellar Network Executor |
| 149 | + │ │ │ |
| 150 | + │ 1. Submit latest signed state │ │ |
| 151 | + │ on-chain (seq=N) │ │ |
| 152 | + │───────────────────────────────▶│ │ |
| 153 | + │ │ │ |
| 154 | + │ 2. Dispute window starts │ │ |
| 155 | + │ (T blocks) │ │ |
| 156 | + │ │ │ |
| 157 | + │ ... T blocks pass, no dispute ... │ |
| 158 | + │ │ │ |
| 159 | + │ 3. Dispute window expires │ │ |
| 160 | + │ │ │ |
| 161 | + │ 4. Funds distributed per │ │ |
| 162 | + │ submitted state │ │ |
| 163 | + │ │ │ |
| 164 | + │ Channel CLOSED │ │ |
| 165 | + │ │ │ |
| 166 | +``` |
| 167 | + |
| 168 | +### 5. Dispute Resolution |
| 169 | + |
| 170 | +``` |
| 171 | +User Stellar Network Executor |
| 172 | + │ │ │ |
| 173 | + │ 1. User submits stale state │ │ |
| 174 | + │ (seq=K, where K < N) │ │ |
| 175 | + │───────────────────────────────▶│ │ |
| 176 | + │ │ │ |
| 177 | + │ 2. Dispute window starts │ │ |
| 178 | + │ │ │ |
| 179 | + │ 3. Executor submits newer │ │ |
| 180 | + │ state (seq=N, N > K) │ │ |
| 181 | + │ │◄───────────────────────────────│ |
| 182 | + │ │ │ |
| 183 | + │ 4. Network validates: │ │ |
| 184 | + │ seq(N) > seq(K) ✓ │ │ |
| 185 | + │ Both signatures valid ✓ │ │ |
| 186 | + │ │ │ |
| 187 | + │ 5. Dispute window resets │ │ |
| 188 | + │ │ │ |
| 189 | + │ ... T blocks pass, no further dispute ... │ |
| 190 | + │ │ │ |
| 191 | + │ 6. Funds distributed per │ │ |
| 192 | + │ state seq=N (latest) │ │ |
| 193 | + │ │ │ |
| 194 | + │ Channel CLOSED │ │ |
| 195 | + │ │ │ |
| 196 | +``` |
| 197 | + |
| 198 | +### 6. Top-Up |
| 199 | + |
| 200 | +``` |
| 201 | +User Stellar Network Executor |
| 202 | + │ │ │ |
| 203 | + │ 1. Deposit additional funds │ │ |
| 204 | + │ to escrow account │ │ |
| 205 | + │───────────────────────────────▶│ │ |
| 206 | + │ │ │ |
| 207 | + │ 2. Sign new state (seq=N+1) │ │ |
| 208 | + │ reflecting increased capacity │ │ |
| 209 | + │───────────────────────────────────────────────────────────────▶│ |
| 210 | + │ │ │ |
| 211 | + │ 3. Executor co-signs │ │ |
| 212 | + │◄───────────────────────────────────────────────────────────────│ |
| 213 | + │ │ │ |
| 214 | + │ Channel remains ACTIVE │ │ |
| 215 | + │ with higher capacity │ │ |
| 216 | + │ │ │ |
| 217 | +``` |
| 218 | + |
| 219 | +## Time-Lock Parameters |
| 220 | + |
| 221 | +| Parameter | Value | Description | |
| 222 | +|-----------|-------|-------------| |
| 223 | +| `DISPUTE_WINDOW` | 720 blocks (~1 hour on Stellar) | Time allowed for counterparty to submit a newer state after unilateral close. | |
| 224 | +| `CHANNEL_EXPIRY` | 525,600 blocks (~365 days) | Maximum channel lifetime. After expiry, either party can force-close. | |
| 225 | +| `MIN_CHANNEL_CAPACITY` | 10 USDC / 50 XLM | Minimum initial deposit to open a channel. | |
| 226 | +| `TOP_UP_COOLDOWN` | 60 blocks (~5 minutes) | Minimum time between top-up operations to prevent spam. | |
| 227 | + |
| 228 | +## Signed State Format |
| 229 | + |
| 230 | +Each off-chain state update is a signed message with the following structure: |
| 231 | + |
| 232 | +```typescript |
| 233 | +interface ChannelState { |
| 234 | + channelId: string; // Escrow account public key |
| 235 | + sequenceNumber: number; // Monotonically increasing, starts at 0 |
| 236 | + userBalance: string; // User's balance in the channel (stroops) |
| 237 | + executorBalance: string; // Executor's balance in the channel (stroops) |
| 238 | + asset: string; // "native" (XLM) or USDC asset code |
| 239 | + expiresAt: number; // Ledger number at which channel expires |
| 240 | + userSignature: string; // User's ed25519 signature |
| 241 | + executorSignature: string; // Executor's ed25519 signature |
| 242 | +} |
| 243 | +``` |
| 244 | + |
| 245 | +### Invariants |
| 246 | + |
| 247 | +1. `userBalance + executorBalance == totalDeposited` (conservation of funds) |
| 248 | +2. `sequenceNumber` is strictly monotonically increasing across updates |
| 249 | +3. Both `userSignature` and `executorSignature` must be valid for a state to be accepted |
| 250 | +4. The state with the **highest valid sequence number** always wins in disputes |
| 251 | +5. `userBalance >= 0` and `executorBalance >= 0` (no negative balances) |
| 252 | + |
| 253 | +## Dispute Resolution Soundness |
| 254 | + |
| 255 | +The dispute mechanism ensures the latest state always prevails: |
| 256 | + |
| 257 | +1. **Ordering**: States are totally ordered by `sequenceNumber`. A state with a higher sequence number is strictly newer. |
| 258 | +2. **Authenticity**: Both parties must sign each state. Neither party can forge a state. |
| 259 | +3. **Finality**: The dispute window gives the counterparty sufficient time to submit a newer state. After the window expires, the pending state becomes final. |
| 260 | +4. **Incentive Compatibility**: Submitting a stale state is unprofitable — the counterparty will always have a newer state to dispute with. The stale-state submitter wastes transaction fees. |
| 261 | +5. **Liveness**: Channel expiry ensures funds are never permanently locked, even if one party goes offline. |
| 262 | + |
| 263 | +## Security Considerations |
| 264 | + |
| 265 | +- **Key Storage**: Channel signing keys should be derived from the user's wallet using BIP-32 derivation to avoid key reuse. |
| 266 | +- **State Backup**: Both parties must persist all signed states locally. Loss of state data may result in accepting an older (less favorable) state during dispute. |
| 267 | +- **Replay Protection**: The `channelId` and `sequenceNumber` together form a unique identifier, preventing cross-channel replay attacks. |
| 268 | +- **Privacy**: Only the open and close transactions appear on-chain. Individual renewal amounts and timing are not visible to blockchain observers. |
0 commit comments