155 lines
8.7 KiB
Markdown
155 lines
8.7 KiB
Markdown
# Plan: UDP Nostr + FIPS Discovery (Combination 1)
|
|
|
|
## Goal
|
|
|
|
Combine UDP Nostr's no-handshake transmission with FIPS's Nostr-mediated endpoint discovery, so that:
|
|
|
|
- The **relay** can move IPs freely and re-advertise its current UDP endpoint on Nostr (kind 37195 advert). Its npub is the stable address.
|
|
- The **sender** looks up the relay's current endpoint by npub, then sends a single raw UDP datagram — no FIPS daemon, no Noise handshake, no connection state.
|
|
- The no-handshake property of UDP Nostr is **fully preserved** on the data plane. FIPS is used only for discovery, never for transport.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Sender Relay
|
|
│ │
|
|
│ 1. nak req -k 37195 -d fips-overlay-v1 │
|
|
│ -a <relay_npub> ──────────────────► │ Nostr relay pool
|
|
│ advert │ (wss://relay.damus.io, etc.)
|
|
│ ◄──────────────────────────────────── │
|
|
│ endpoints: [{udp, 203.0.113.45:8889}] │
|
|
│ │
|
|
│ 2. extract udp endpoint from advert │
|
|
│ │
|
|
│ 3. nak event -k 1 -c "..." --sec <key> │
|
|
│ | python3 src/udp_nostr_send.py │
|
|
│ 203.0.113.45 8889 │
|
|
│ ──────── single UDP datagram ────────► │ src/udp_nostr_recv.py
|
|
│ (no handshake) │ (4-line listener)
|
|
│ │
|
|
│ when IP changes:
|
|
│ relay re-publishes kind 37195
|
|
│ with new endpoint
|
|
```
|
|
|
|
### What runs where
|
|
|
|
| Component | Runs FIPS? | Runs UDP Nostr? | Role |
|
|
|-----------|:----------:|:---------------:|------|
|
|
| Sender | No | Yes (send) | Fetches advert, sends raw UDP datagram |
|
|
| Relay | Yes (discovery only) | Yes (recv) | Publishes kind 37195 advert, listens for UDP datagrams |
|
|
| Nostr relays | — | — | Carry the kind 37195 adverts (existing public relays) |
|
|
|
|
The relay does **not** need to run the full FIPS mesh. It only needs the discovery/advert-publishing piece. Two implementation paths for the relay side:
|
|
|
|
- **Path A (minimal):** A small script that publishes kind 37195 adverts using `nak` and runs the existing 4-line [`src/udp_nostr_recv.py`](../src/udp_nostr_recv.py). No FIPS daemon at all — just re-use FIPS's advert *format* and the public Nostr relay pool.
|
|
- **Path B (full FIPS):** Run the FIPS daemon with `transports.udp.advertise_on_nostr: true` and `node.discovery.nostr.advertise: true`, but **do not accept FMP connections** — only use FIPS for advert publishing + NAT-traversal + auto-re-advertise on IP change. The UDP Nostr listener runs alongside it on a separate port.
|
|
|
|
Path A is simpler and keeps the relay side dependency-free. Path B gives you automatic re-advertisement on IP change and STUN-based NAT traversal for free. Start with Path A, upgrade to Path B if the relay is behind NAT.
|
|
|
|
## Implementation steps
|
|
|
|
### Step 1 — Relay: publish endpoint advert (Path A, minimal)
|
|
|
|
Write a script `udp_nostr_relay_advertise.sh` that:
|
|
|
|
1. Takes the relay's Nostr secret key, the UDP port it listens on, and its public IP (or auto-detect via an echo service).
|
|
2. Constructs a kind 37195 event with:
|
|
- `d` tag: `fips-overlay-v1`
|
|
- `protocol` tag: `fips-overlay-v1`
|
|
- `version` tag: `1`
|
|
- `expiration` tag: now + 3600
|
|
- content JSON: `{"identifier":"fips-overlay-v1","version":1,"endpoints":[{"transport":"udp","addr":"<ip>:<port>"}]}`
|
|
3. Signs and publishes with `nak event -k 37195 --sec <key>` piped to `nak publish` (or `nostcat`).
|
|
4. Re-runs on a timer (e.g. every 30 min, or on IP-change detection) to keep the advert fresh.
|
|
|
|
**Deliverable:** `udp_nostr_relay_advertise.sh` in this repo.
|
|
|
|
### Step 2 — Relay: run the UDP Nostr listener
|
|
|
|
No changes needed — the existing [`src/udp_nostr_recv.py`](../src/udp_nostr_recv.py) already does this. Document that the relay runs:
|
|
|
|
```bash
|
|
python3 src/udp_nostr_recv.py 0.0.0.0 8889
|
|
```
|
|
|
|
alongside the advert publisher from Step 1.
|
|
|
|
**Deliverable:** updated README section showing the relay-side setup.
|
|
|
|
### Step 3 — Sender: resolve endpoint from advert
|
|
|
|
Write a script `udp_nostr_resolve.sh` (or `.py`) that:
|
|
|
|
1. Takes the relay's npub and an optional list of Nostr relays (default: the FIPS defaults — `wss://relay.damus.io`, `wss://nos.lol`, `wss://offchain.pub`).
|
|
2. Runs `nak req -k 37195 -d fips-overlay-v1 -a <relay_npub>` against the relays.
|
|
3. Parses the returned event's content JSON, extracts the first `udp` endpoint's `addr` field.
|
|
4. Prints `ip port` to stdout (suitable for piping into `src/udp_nostr_send.py`).
|
|
|
|
**Deliverable:** `udp_nostr_resolve.py` in this repo.
|
|
|
|
### Step 4 — Sender: one-shot send-via-discovery command
|
|
|
|
Write a wrapper `src/udp_nostr_send_via_npub.sh` that chains resolve + send:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# Usage: echo "hello" | src/udp_nostr_send_via_npub.sh <relay_npub> [relay_url ...]
|
|
NPUB=$1; shift
|
|
ENDPOINT=$(python3 src/udp_nostr_resolve.py "$NPUB" "$@")
|
|
nak event -k 1 -c "$(cat)" --sec $(nak key generate) \
|
|
| python3 src/udp_nostr_send.py $ENDPOINT
|
|
```
|
|
|
|
**Deliverable:** `src/udp_nostr_send_via_npub.sh` in this repo.
|
|
|
|
### Step 5 — Test end-to-end
|
|
|
|
1. Start the relay: `src/udp_nostr_recv.py` + `src/udp_nostr_relay_advertise.sh` with a test key.
|
|
2. From a separate machine (or loopback), run `src/udp_nostr_send_via_npub.sh <relay_npub>` with a test message.
|
|
3. Verify the message arrives at the relay.
|
|
4. Change the relay's advertised IP (simulate move), re-publish, and verify the sender picks up the new endpoint on the next resolve.
|
|
|
|
**Deliverable:** updated [`src/test_local.sh`](../src/test_local.sh) or a new `src/test_discovery.sh`.
|
|
|
|
### Step 6 — Document the combination
|
|
|
|
Add a section to [`README.md`](README.md:1) (or a new `discovery.md`) explaining:
|
|
|
|
- The problem this solves (endpoint blocking / relay mobility).
|
|
- The architecture diagram above.
|
|
- That FIPS is used for discovery only — the data plane remains handshake-free.
|
|
- The residual exposure: the sender's Nostr relay lookup is a TCP/WSS handshake. Mitigations: cache adverts, use Tor for the lookup, or embed the endpoint in DNS (cross-reference [`docs/passive_sniffing_relay.md`](../docs/passive_sniffing_relay.md) Model B).
|
|
|
|
**Deliverable:** new documentation section.
|
|
|
|
## Open questions / decisions
|
|
|
|
1. **Advert kind reuse vs. new kind.** Reusing kind 37195 with `d=fips-overlay-v1` means FIPS nodes will see these adverts and may try to FMP-handshake the relay. Options:
|
|
- (a) Use a different `d` tag (e.g. `udp-nostr-v1`) so FIPS nodes ignore it. **Recommended** — cleanest separation.
|
|
- (b) Use a different kind entirely (e.g. a new application-defined replaceable kind). More work, less reuse.
|
|
- (c) Accept the stray handshake attempts; the relay just drops them (it's not running FMP). Noisy but harmless.
|
|
|
|
2. **Advert freshness vs. relay churn.** If the relay's IP changes but it can't re-publish (no connectivity to Nostr relays), senders will have a stale endpoint. Mitigation: short `expiration` (e.g. 15 min) + frequent re-publish. The sender falls back to cached adverts or fails gracefully.
|
|
|
|
3. **Sender-side relay-list bootstrapping.** The sender needs at least one Nostr relay URL to start the lookup. This is a small bootstrap problem — solved by shipping defaults (the FIPS list) or embedding a relay URL in a DNS TXT record / the relay's npub profile (kind 0).
|
|
|
|
## What this does NOT do
|
|
|
|
- It does **not** run FIPS on the sender. The sender is still just `nak` + 4 lines of Python.
|
|
- It does **not** add a handshake to the data plane. The UDP datagram is still fire-and-forget, signature-only.
|
|
- It does **not** hide the sender's lookup of the relay endpoint. That lookup is a normal Nostr REQ over WSS (or Tor, if the sender chooses). This is a separate problem from the data-plane censorship resistance.
|
|
- It does **not** provide two-way communication. The sender cannot receive replies unless the relay knows where to send them (which would require a handshake or a return-address in the event).
|
|
|
|
## File summary
|
|
|
|
| File | Status | Purpose |
|
|
|------|--------|---------|
|
|
| `src/udp_nostr_relay_advertise.sh` | new | Relay: publish kind 37195 endpoint advert |
|
|
| `src/udp_nostr_resolve.py` | new | Sender: fetch relay's current UDP endpoint by npub |
|
|
| `src/udp_nostr_send_via_npub.sh` | new | Sender: one-shot resolve + send |
|
|
| `src/udp_nostr_recv.py` | existing | Relay: unchanged 4-line listener |
|
|
| `src/udp_nostr_send.py` | existing | Sender: unchanged 4-line sender |
|
|
| `src/test_discovery.sh` | new | End-to-end test |
|
|
| `README.md` | update | Document the discovery combination |
|