Skip to content

Commit deff844

Browse files
JOYclaude
andcommitted
feat: add payment channel state machine and protocol spec
Design the full lifecycle protocol specification for Stellar-based payment channels used for private recurring subscription payments. Includes state machine diagram, sequence diagrams for all flows (cooperative close, unilateral close, dispute, top-up), time-lock parameters, and dispute resolution soundness analysis. Closes #843 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1332c9f commit deff844

1 file changed

Lines changed: 268 additions & 0 deletions

File tree

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)