upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr.cpp')
-rw-r--r--src/nostr.cpp110
1 files changed, 110 insertions, 0 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 59817f5..44aec56 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -31,6 +31,13 @@ static const int BTF_DESC_KIND = 38501;
31// the abandoned test .btf descriptors lingering on the relays become invisible. 31// the abandoned test .btf descriptors lingering on the relays become invisible.
32static const char* BTF_DESC_DTAG = "btf-descriptor-2"; 32static const char* BTF_DESC_DTAG = "btf-descriptor-2";
33 33
34// Relay auto-discovery: a relay operator announces their relay as an addressable
35// event (kind 38502) so every node learns it automatically -- no manual
36// seed-list edit, no maintainer approval. Volunteer relays just work.
37static const int BTF_RELAY_KIND = 38502;
38static const char* BTF_RELAY_DTAG = "btf-relay-2";
39static const size_t BTF_MAX_DISCOVERED_RELAYS = 50; // anti-spam cap
40
34// Nostr network/scope for Bitflash. Events are replaceable (NIP-78). 41// Nostr network/scope for Bitflash. Events are replaceable (NIP-78).
35static const int NOSTR_KIND = 30078; 42static const int NOSTR_KIND = 30078;
36static const char* NOSTR_DTAG = "bitflash-mainnet-2"; 43static const char* NOSTR_DTAG = "bitflash-mainnet-2";
@@ -82,6 +89,48 @@ void BtfSetActiveRelay(const string& relay)
82 strBtfActiveRelay = relay; 89 strBtfActiveRelay = relay;
83} 90}
84 91
92// A relay this node advertises on Nostr for others to discover (/announcerelay).
93string strBtfAnnounceRelay;
94// Relays learned from other operators' Nostr announcements.
95static set<string> g_discoveredRelays;
96static CCriticalSection cs_discoveredRelays;
97
98// Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host.
99static bool LooksLikeRelay(const string& r)
100{
101 size_t colon = r.rfind(':');
102 if (colon == string::npos || colon == 0 || colon + 1 >= r.size())
103 return false;
104 if (r.size() > 100)
105 return false;
106 for (size_t i = colon + 1; i < r.size(); i++)
107 if (!isdigit((unsigned char)r[i]))
108 return false;
109 int port = atoi(r.c_str() + colon + 1);
110 if (port <= 0 || port > 65535)
111 return false;
112 // host: allow letters, digits, dot, dash, colon (IPv6 not supported yet)
113 for (size_t i = 0; i < colon; i++)
114 {
115 char c = r[i];
116 if (!(isalnum((unsigned char)c) || c == '.' || c == '-'))
117 return false;
118 }
119 return true;
120}
121
122// Every relay to try: curated seeds first (reliable bootstrap), then the ones
123// discovered from Nostr announcements (volunteer relays).
124vector<string> BtfAllRelays()
125{
126 vector<string> all = vBtfMeetingRelays;
127 CRITICAL_BLOCK(cs_discoveredRelays)
128 foreach(const string& r, g_discoveredRelays)
129 if (find(all.begin(), all.end(), r) == all.end())
130 all.push_back(r);
131 return all;
132}
133
85 134
86// 135//
87// Utilities 136// Utilities
@@ -560,6 +609,21 @@ static void PublishDescriptor(CWebSocket& ws, CNostrKey& key)
560 } 609 }
561} 610}
562 611
612// Announce our relay (from /announcerelay=host:port) so every node discovers it
613// automatically -- volunteer relays join the network with no maintainer step.
614static void PublishRelayAnnouncement(CWebSocket& ws, CNostrKey& key)
615{
616 if (strBtfAnnounceRelay.empty())
617 return;
618 json tags = json::array({ json::array({ "d", BTF_RELAY_DTAG }) });
619 json ev;
620 if (BuildSignedEvent(key, BTF_RELAY_KIND, tags, strBtfAnnounceRelay, ev))
621 {
622 json pub = json::array({ "EVENT", ev });
623 ws.SendText(pub.dump());
624 }
625}
626
563// Resolve a `.btf` address via a relay: fetch the descriptor published by that 627// Resolve a `.btf` address via a relay: fetch the descriptor published by that
564// address's key and verify it self-certifies under the decoded pubkey. 628// address's key and verify it self-certifies under the decoded pubkey.
565static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, 629static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
@@ -654,6 +718,9 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
654 // Publish our self-certifying .btf descriptor (rendezvous discovery) 718 // Publish our self-certifying .btf descriptor (rendezvous discovery)
655 PublishDescriptor(ws, key); 719 PublishDescriptor(ws, key);
656 720
721 // If we run a relay, announce it so others discover it automatically
722 PublishRelayAnnouncement(ws, key);
723
657 // Subscribe to receive the announcements of other nodes 724 // Subscribe to receive the announcements of other nodes
658 json filter = json::object(); 725 json filter = json::object();
659 filter["kinds"] = json::array({ NOSTR_KIND }); 726 filter["kinds"] = json::array({ NOSTR_KIND });
@@ -736,6 +803,49 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
736 } 803 }
737 } 804 }
738 } 805 }
806
807 // Discover volunteer relays announced by other operators (kind 38502). Nodes
808 // add these to their failover list automatically, so anyone can strengthen
809 // the network without a maintainer editing the seed list. Bad/dead relays
810 // just fail to pair and get skipped -- and a relay is trustless anyway (it
811 // only forwards echan ciphertext and can't read or MITM the traffic).
812 ws.SendText(json::array({ "CLOSE", "btf-disc" }).dump());
813 {
814 json rfilter = json::object();
815 rfilter["kinds"] = json::array({ BTF_RELAY_KIND });
816 rfilter["#d"] = json::array({ BTF_RELAY_DTAG });
817 rfilter["limit"] = 200;
818 json rreq = json::array({ "REQ", "btf-relays", rfilter });
819 if (ws.SendText(rreq.dump()))
820 {
821 for (;;)
822 {
823 string msg;
824 if (!ws.RecvText(msg))
825 break;
826 json j;
827 try { j = json::parse(msg); } catch (...) { continue; }
828 if (!j.is_array() || j.empty()) continue;
829 string type = j[0].get<string>();
830 if (type == "EVENT" && j.size() >= 3)
831 {
832 const json& ev = j[2];
833 if (!ev.contains("content") || !ev.contains("kind")) continue;
834 if (ev["kind"].get<int>() != BTF_RELAY_KIND) continue;
835 string r = ev["content"].get<string>();
836 if (!LooksLikeRelay(r)) continue;
837 bool fNew = false;
838 CRITICAL_BLOCK(cs_discoveredRelays)
839 if (g_discoveredRelays.size() < BTF_MAX_DISCOVERED_RELAYS)
840 fNew = g_discoveredRelays.insert(r).second;
841 if (fNew)
842 printf("Nostr: discovered relay %s\n", r.c_str());
843 }
844 else if (type == "EOSE")
845 break;
846 }
847 }
848 }
739 return true; 849 return true;
740} 850}
741 851