# GRASP Mirror NIP-46 Remote Signing — Implementation Plan ## Goal Build and deploy the grasp-mirror daemon with NIP-46 remote signing so it can publish authenticated `kind:30618` state events and push git data to GRASP servers, without storing nsec keys on the VPS. ## Architecture ``` ┌─────────────┐ │ Amber │ │ (phone) │ └──────┬──────┘ │ NIP-46 (kind:24133) │ NIP-04 encrypted ┌──────┴──────┐ │ Nostr Relay │ │ (relay/ngit)│ └──────┬──────┘ │ ┌──────┴──────┐ │ grasp-mirror│ │ daemon │ │ (VPS) │ └──────┬──────┘ │ git push + kind:30618 ┌──────┴──────┐ │ GRASP │ │ servers │ └─────────────┘ ``` - Daemon generates client keypairs per npub, persists in SQLite - Produces `nostrconnect://` pairing URIs shown in health endpoint - User opens URI in Amber → NIP-46 session established - Before git push, daemon builds unsigned `kind:30618` from repo refs - Sends `sign_event` request to signer via NIP-46 relay protocol - Amber signs on phone → daemon receives signed event → publishes + pushes ## Checklist ### Phase 1: Fix compile errors locally - [ ] Fix `nip46.rs` — `notification.as_ref()` → match on owned `RelayPoolNotification` - [ ] Fix `nip46.rs` — ensure `message.id()` borrow is cloned before consuming message - [ ] Fix `git_mirror.rs` — accept shared `nostr_sdk::Client` instead of creating throwaway per server - [ ] Fix `main.rs` — pass `nostr_client` to `git_mirror.mirror_repo_to_servers` - [ ] Verify `db.rs` — `bool` FromRow works with sqlx 0.8 sqlite INTEGER - [ ] Pin Rust 1.95.0 in Ansible role `tasks/main.yml` - [ ] Commit and push to GRASP origin ### Phase 2: Build on VPS - [ ] GRASP server running on target VPS (dependency: migration session) - [ ] Push SSH key to VPS for Ansible key-based auth - [ ] Run `ansible-playbook playbooks/30-grasp-mirror.yml -v` - [ ] Fix any remaining compile errors iteratively - [ ] Binary installed at `/usr/local/bin/grasp-mirror` - [ ] Systemd service `grasp-mirror` running ### Phase 3: Verify and pair - [ ] Health endpoint responds: `curl http://localhost:7335/health` - [ ] Response includes `nip46` array with session statuses - [ ] Each unpaired npub shows `pairing_uri` - [ ] Pair npub 1 (`npub12m5...`) via Amber - [ ] Pair npub 2 (`npub19jx...`) via Amber - [ ] Pair npub 3 (`npub1c03...`) via Amber - [ ] Health endpoint shows all 3 sessions `connected: true` - [ ] Test signing: daemon builds and signs a `kind:30618` event - [ ] Test push: git data pushes to a target GRASP server ### Phase 4: Document roadmap - [ ] Add QEMU/OpenWRT orchestration roadmap to PLAN.md or PROGRESS.md ## Key Files | File | Role | |---|---| | `src/nip46.rs` | NIP-46 client: sessions, NIP-04 encrypt/decrypt, relay listener, `sign_event()` | | `src/db.rs` | `nip46_sessions` table + CRUD methods | | `src/config.rs` | `Nip46Config` with relays + signing_timeout_secs | | `src/git_mirror.rs` | Builds unsigned `kind:30618`, signs via NIP-46, publishes before push | | `src/http_health.rs` | NIP-46 session status in `/health` JSON | | `src/main.rs` | Wires NIP-46 init + listener into daemon startup | | `ansible/roles/grasp_mirror/tasks/main.yml` | Ansible deploy tasks (build + install) | | `ansible/roles/grasp_mirror/defaults/main.yml` | NIP-46 relay + timeout vars | | `ansible/roles/grasp_mirror/templates/config.toml.j2` | Config with `[nip46]` section | ## Constraints - No `any` type in TypeScript (N/A — Rust project) - No comments in code unless requested - Use `pnpm` for JS, `cargo` for Rust - Rust 1.95.0 required (nostr-sdk 0.39 doesn't compile on 1.85) - NIP-04 encryption for NIP-46 messages (not NIP-44 — simpler, Amber supports it) - Signing timeout: 7 days (604800 seconds) — queue-and-wait model - GRASP push auth requires signed `kind:30618` by maintainer, not HTTP auth ## Future Roadmap: QEMU/OpenWRT Test Orchestration After migration, repurpose old VPS (2 vCPU, 8GB RAM, 99GB disk) as dedicated OpenWRT test runner: 1. **Deploy loom-worker** on old VPS — decentralized compute marketplace via Nostr 2. **Write QEMU execution adapter** for loom — Unix domain socket that spins up OpenWRT instances, runs tests, streams results 3. **Use existing act-runner** custom pipeline to submit loom jobs from CI 4. **Do NOT build custom orchestration** — loom already has standardized event kinds (5001/5100/5101), Cashu payments, Blossom result storage, encrypted communication 5. **Evaluate loom from budabit** (`nostr://npub1hw6amg8p24ne08c9gdq8hhpqx0t0pwanpae9z25crn7m9uy7yarse465gr/relay.ngit.dev/loom-worker`) before building anything custom Loom's pluggable adapter architecture means we write ~200 lines of Deno/TypeScript for the QEMU adapter and get the entire Nostr job queue, payment, and result streaming for free.