upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/PLAN.md
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-16 15:32:55 +0530
committerYour Name <you@example.com>2026-05-16 15:32:55 +0530
commit133e40c82afb4d7659758b1fa57925ac57af4621 (patch)
tree130f43e668f42f8fcc8ef55808ba89b6db40f615 /PLAN.md
parent8f0aeb7d7b8216f1fc906cf855e5be9e90ecc0a8 (diff)
Phase 3: on-device Cashu wallet with mbedTLS secp256k1 + SPIFFS persistence + PSRAM
- wallet.c/h: secp256k1 ECP primitives (hash_to_curve, scalar_mul, point_add) - wallet_persist.c/h: SPIFFS persistence with threshold-based write protection - Fee accounting for swap (input_fee_ppk from /v1/keysets) - Keyset fetch via /v1/keysets (586 bytes vs 21KB for /v1/keys) - Wallet API: GET /wallet, POST /wallet/swap, POST /wallet/send - Payment proofs auto-stored to wallet + persisted on SPIFFS - PSRAM enabled for large allocations (ESP32-S3 has 8MB) - Wallet init deferred to dedicated task (avoids sys_evt stack overflow) - Cashu proof ID buffer size fixed (66 hex chars, not 16) - HTTP client: added fetch_headers() call for proper response handling - persist_threshold_sats config parameter (default: 1 sat)
Diffstat (limited to 'PLAN.md')
-rw-r--r--PLAN.md96
1 files changed, 73 insertions, 23 deletions
diff --git a/PLAN.md b/PLAN.md
index d43344b..2af8a39 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -2,12 +2,12 @@
2 2
3## Overview 3## Overview
4 4
5Build a TollGate firmware for two ESP32 devices, following the [TollGate protocol spec](https://github.com/OpenTollGate/tollgate) (TIP-01, TIP-02, HTTP-01/02/03). The implementation uses ESP-IDF (C/C++) and integrates the nucula Cashu wallet. 5Build a TollGate firmware for two ESP32 devices, following the [TollGate protocol spec](https://github.com/OpenTollGate/tollgate) (TIP-01, TIP-02, HTTP-01/02/03). The implementation uses ESP-IDF (C/C++) with an on-device Cashu wallet using mbedTLS secp256k1.
6 6
7## Architecture Decision: C/C++ (ESP-IDF) 7## Architecture Decision: C/C++ (ESP-IDF)
8 8
9- Existing working captive portal is in C (ESP-IDF) 9- Existing working captive portal is in C (ESP-IDF)
10- Nucula Cashu wallet is in C/C++ (ESP-IDF) 10- On-device Cashu wallet uses mbedTLS secp256k1 (hardware RNG, software ECP)
11- ESP-IDF is already installed at `~/esp/esp-idf` 11- ESP-IDF is already installed at `~/esp/esp-idf`
12- No Rust/ESP32 toolchain installed 12- No Rust/ESP32 toolchain installed
13 13
@@ -16,11 +16,12 @@ Build a TollGate firmware for two ESP32 devices, following the [TollGate protoco
16| Layer | Technology | 16| Layer | Technology |
17|-------|-----------| 17|-------|-----------|
18| Framework | ESP-IDF v5.4.1 (C/C++) | 18| Framework | ESP-IDF v5.4.1 (C/C++) |
19| Cashu wallet | nucula `Wallet` class (Phase 3) | 19| Cashu wallet | Custom mbedTLS secp256k1 wallet (hash_to_curve, blind signing, swap, send) |
20| HTTP server | `esp_http_server` (port 80 captive portal, port 2121 TollGate API) | 20| HTTP server | `esp_http_server` (port 80 captive portal, port 2121 TollGate API + wallet) |
21| DNS | Custom UDP task (hijack unauthenticated, forward authenticated) | 21| DNS | Custom UDP task (hijack unauthenticated, forward authenticated) |
22| NAT | lwIP NAPT | 22| NAT | lwIP NAPT |
23| Testing | Playwright + curl + pyserial | 23| Persistence | SPIFFS (960K partition) with threshold-based write protection |
24| Testing | Playwright + curl + nutshell CLI |
24| Build | Makefile | 25| Build | Makefile |
25 26
26## Four-Phase Plan 27## Four-Phase Plan
@@ -52,7 +53,7 @@ Build a TollGate firmware for two ESP32 devices, following the [TollGate protoco
52| 13 | Reset auth | GET /reset_authentication | 200 | PASS | 53| 13 | Reset auth | GET /reset_authentication | 200 | PASS |
53| 14 | Internet blocked after reset | ping 8.8.8.8 | Fails | PASS | 54| 14 | Internet blocked after reset | ping 8.8.8.8 | Fails | PASS |
54 55
55### Phase 2: E-Cash Payments — IN PROGRESS 56### Phase 2: E-Cash Payments — COMPLETE
56 57
57**Goal:** Replace free access with Cashu payment. ESP32 parses token, checks proof state via mint API, grants time-based session. 58**Goal:** Replace free access with Cashu payment. ESP32 parses token, checks proof state via mint API, grants time-based session.
58 59
@@ -79,29 +80,78 @@ Build a TollGate firmware for two ESP32 devices, following the [TollGate protoco
79| 26 | Client isolation | Only payer gets internet | Non-payer blocked | Phase 3 | 80| 26 | Client isolation | Only payer gets internet | Non-payer blocked | Phase 3 |
80| 27 | Full e2e: portal→pay→browse | Playwright | Complete flow | Phase 3 | 81| 27 | Full e2e: portal→pay→browse | Playwright | Complete flow | Phase 3 |
81 82
82**Captive Portal Fix:** Added DoT reject server on port 853 (TCP RST forces DNS-over-TLS fallback to plain DNS), DNS hijack returns NXDOMAIN for all non-A query types, explicit 302 redirect handlers for all captive detection URIs. Needs verification with actual GrapheneOS phone. 83**Captive Portal Detection:** DoT reject server on port 853, NXDOMAIN for non-A queries, 302 redirects for captive URIs. Verified working on GrapheneOS (commit `236b61d`).
83 84
84### Phase 3: nucula Wallet + ESP32-to-ESP32 Payments — NOT STARTED 85### Phase 3: On-Device Wallet + ESP32-to-ESP32 Payments — IN PROGRESS
85 86
86**Goal:** Integrate nucula's full Cashu wallet. ESP32 holds balance, can be a reseller. ESP32-to-ESP32 direct payments. 87**Goal:** On-device Cashu wallet using mbedTLS secp256k1. ESP32 holds balance, can swap proofs, create tokens for P2P payments. Proof persistence via SPIFFS with threshold-based write protection.
87 88
88**11 Additional Test Cases:** 89#### Wallet Architecture
89| # | Test | Method | Pass Criteria | 90
90|---|------|--------|---------------| 91- **Crypto**: mbedTLS secp256k1 (software ECP, hardware RNG via `esp_fill_random`)
91| 28 | Wallet boot | Serial | Keysets loaded | 92- **Blind signing**: `hash_to_curve()` (SHA256 try-and-increment), `scalar_mul()`, `point_add()`
92| 29 | Receive via wallet | POST :2121/ | Balance incremented | 93- **Unblinding**: `C = C_ + (order - r) * G` — avoids needing mint's public key K, avoids point negation
93| 30 | Balance persists | Reboot | Same balance | 94- **Proof storage**: In-memory array (50 max), persisted to SPIFFS JSON
94| 31 | Payout routine | Wait + serial | Tokens melted to LN | 95- **Persistence**: SPIFFS `/spiffs/wallet.json`, only written when `balance >= persist_threshold_sats`
95| 32 | Reseller discover | Serial | Upstream TollGate found | 96- **Keyset fetch**: GET /v1/keys from mint on boot
96| 33 | Reseller pay | Serial + API | Token POSTed upstream | 97- **Swap**: POST /v1/swap — reissues proofs with new secrets
97| 34 | Multi-hop internet | Ping from laptop | laptop→A→B→internet | 98- **Token creation**: Encode proofs as `cashuA` base64url token
98| 35 | P2PK receive | Post P2PK token | Auto-signed, accepted | 99
99| 36 | DLEQ verified | Post token with DLEQ | Verified, accepted | 100#### Wallet Endpoints (on :2121)
100| 37 | 5 consecutive payments | Loop | All authenticated | 101
101| 38 | Stress: rapid pay/expire | Loop with short sessions | No crash/leak | 102| Method | Path | Description |
103|--------|------|-------------|
104| GET | /wallet | Balance, proof count, keyset count |
105| POST | /wallet/swap | Swap all proofs for fresh ones via mint |
106| POST | /wallet/send | Create cashuA token for given amount (body = sat count) |
107
108#### Payment Integration
109
110Received payment proofs are automatically added to wallet after session creation in `tollgate_api.c`.
111
112#### Persistence Threshold
113
114Config parameter `persist_threshold_sats` (default: 1) controls when wallet state is written to flash:
115- `balance >= persist_threshold_sats` → write wallet.json
116- `balance < threshold` → skip write (or delete existing file)
117- Rationale: flash has finite write cycles (~100K erase per sector); only persist when e-cash value justifies the wear cost
118- SPIFFS wear-leveling spreads writes across the 960K partition
119
120#### Test Cases
121
122| # | Test | Method | Pass Criteria | Status |
123|---|------|--------|---------------|--------|
124| 28 | Wallet boot | Serial | Keysets loaded | TODO |
125| 29 | Receive via wallet | POST :2121/ | Balance incremented | TODO |
126| 30 | Wallet swap | POST /wallet/swap | Same balance, new proofs | TODO |
127| 31 | Wallet send | POST /wallet/send | Valid cashuA token returned | TODO |
128| 32 | Persistence survives reboot | Reboot + GET /wallet | Same balance | TODO |
129| 33 | Cross-board payment | B sends → A receives | A balance increases | TODO |
130| 34 | Two clients pay independently | Two POSTs | Both authenticated | TODO |
131| 35 | Client isolation | Only payer gets internet | Non-payer blocked | TODO |
132| 36 | Full e2e: portal→pay→browse | Playwright | Complete flow | TODO |
133| 37 | 5 consecutive payments | Loop | All authenticated | TODO |
134| 38 | Stress: rapid pay/expire | Loop with short sessions | No crash/leak | TODO |
102 135
103### Phase 4: ESP32-to-OpenWRT TollGate Interop — NOT STARTED 136### Phase 4: ESP32-to-OpenWRT TollGate Interop — NOT STARTED
104 137
105**Goal:** ESP32 can pay OpenWRT TollGate using Cashu tokens. Full interoperability with existing OpenWRT-based TollGate infrastructure. 138**Goal:** ESP32 can pay OpenWRT TollGate using Cashu tokens. Full interoperability with existing OpenWRT-based TollGate infrastructure.
106 139
107## Total: 38 Tests across 4 phases 140## Total: 38 Tests across 4 phases
141
142## Key Technical Notes
143
144### mbedTLS 3.x Compatibility
145- `mbedtls_ecp_point` is opaque — cannot access `.X`, `.Y`, `.Z` directly
146- Use `mbedtls_ecp_muladd`, `mbedtls_ecp_mul`, `mbedtls_ecp_point_read/write_binary`
147- No point negation needed with `C = C_ + (order - r) * G` unblinding approach
148
149### Board Configuration
150- Board A: `/dev/ttyACM0`, MAC `94:a9:90:2e:37:7c`, SSID `TollGate-377C`, AP IP `10.55.85.1`
151- Board B: `/dev/ttyACM1`, MAC `fc:01:2c:c5:50:50`, unique SSID/IP derived from MAC
152- Both boards run identical firmware, unique config derived at boot from factory MAC
153
154### Test Mint
155- `testnut.cashu.space` — auto-pays lightning invoices for testing
156- `cashu -h https://testnut.cashu.space invoice <amount>` → auto-paid
157- `cashu -h https://testnut.cashu.space send --legacy <amount>` → generates cashuA token