Files
udp_nostr/plans/udp_nostr_fips_discovery.md
2026-08-12 16:34:22 -04:00

8.7 KiB

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. 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 already does this. Document that the relay runs:

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:

#!/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 or a new src/test_discovery.sh.

Step 6 — Document the combination

Add a section to README.md (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 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