upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-29 11:52:12 -0300
committerGitHub <noreply@github.com>2026-07-29 11:52:12 -0300
commit96a8e42bbc313f96d54377ae5886da84298e61a3 (patch)
tree36bcd61016ee03c90140b407d4e5863a282e78cf
parent7f6fdbab6c90360ab3b2d391a7b5aedc6f9721ac (diff)
Remember .btf peers that answered, and dial them first on start (#25)
* net: remember .btf peers that answered, and dial them first on start Discovery depends entirely on the Nostr relays. A node that has been up for hours knows exactly which peers respond, and throws all of it away on exit; the next start reaches for the relays again and walks the same descriptor list, of which roughly 96% is dead. That is what makes a restart take minutes before the first tunnel opens -- measured today at about seven. Every peer that completes a tunnel is now written to btfpeers.json with its meeting node and encryption key, which is everything needed to redial it. On start a thread tries those before the relays have answered anything, stopping once eight are connected. Entries older than a week are dropped, and the file holds at most fifty. Cache only: nothing here changes how a first-ever run bootstraps, and a missing or corrupt file just falls through to relay discovery. * net: exchange signed .btf descriptors between peers Discovery has depended entirely on the Nostr relays. Two connected nodes never told each other who else existed, so a relay outage left a running network unable to grow, and a node that found one stale peer had no way to learn there was anything better. The old `addr` gossip went out with the IRC seed. Peers now trade signed descriptors over a new "btfpeers" message: our own first, then peers that actually answered us. The receiver checks each Schnorr signature against the key the `.btf` address decodes to, so the sender is trusted for nothing -- forging an entry needs somebody else's secret key. That leaves flooding with self-generated keys, which the per-message caps and the "peers that answered" ordering are there to blunt. Unknown commands are ignored (main.cpp), so nodes without this just drop the message; nothing here is a protocol break. The cache from the peer-cache change now keeps each peer's descriptor verbatim alongside the parsed fields, because that signature is what makes a peer relayable. Entries without one -- resolved through Nostr, or written before this -- stay dialable, just not passed on. A dial never clears a descriptor already on file. * main_gui: implement /port, which net.cpp already documented net.cpp has said nListenPort is "tunable via /port" since it was written, but nothing ever parsed the option. Two nodes could not share a machine, which is exactly what testing peer exchange needs. Sets addrLocalHost.port too, so we do not advertise a port we never bound.
-rw-r--r--src/main.cpp31
-rw-r--r--src/main_gui.cpp18
-rw-r--r--src/net.cpp280
-rw-r--r--src/net.h19
-rw-r--r--src/nostr.cpp30
-rw-r--r--src/nostr.h8
6 files changed, 386 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
index a75bf54..bb317d2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1877,6 +1877,14 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1877 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0)); 1877 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0));
1878 } 1878 }
1879 1879
1880 // Introduce ourselves and hand over the .btf peers that answered us.
1881 // Everything in here is self-certifying, so this is safe to send to a
1882 // node we know nothing about, and safe for it to act on.
1883 vector<string> vPexOut;
1884 BtfPexCollect(vPexOut);
1885 if (!vPexOut.empty())
1886 pfrom->PushMessage("btfpeers", vPexOut);
1887
1880 if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str()); 1888 if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str());
1881 } 1889 }
1882 1890
@@ -1888,6 +1896,29 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1888 } 1896 }
1889 1897
1890 1898
1899 else if (strCommand == "btfpeers")
1900 {
1901 // Peer exchange. Nothing here is taken on trust: BtfPexAccept checks
1902 // every descriptor's signature against the key its .btf address
1903 // decodes to, and drops the rest.
1904 int64 nNow = GetTime();
1905 if (nNow - pfrom->nLastPexRecv < PEX_MIN_INTERVAL)
1906 {
1907 if (LogAcceptsCategory("net"))
1908 printf("btfpeers: ignoring, %s is sending them too fast\n",
1909 pfrom->addr.ToString().c_str());
1910 return true;
1911 }
1912 pfrom->nLastPexRecv = nNow;
1913
1914 vector<string> vDesc;
1915 vRecv >> vDesc;
1916 int nKept = BtfPexAccept(vDesc);
1917 LogPrint("net", "btfpeers: kept %d of %zu descriptor(s) from %s\n",
1918 nKept, vDesc.size(), pfrom->addr.ToString().c_str());
1919 }
1920
1921
1891 else if (strCommand == "inv") 1922 else if (strCommand == "inv")
1892 { 1923 {
1893 vector<CInv> vInv; 1924 vector<CInv> vInv;
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 842b0d5..edc772d 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -58,6 +58,7 @@ static void PrintUsage()
58 printf(" /connectbtf=PEER_BTF_ADDRESS\n"); 58 printf(" /connectbtf=PEER_BTF_ADDRESS\n");
59 printf(" /rvrelay=HOST:PORT\n"); 59 printf(" /rvrelay=HOST:PORT\n");
60 printf(" /announcerelay=HOST:PORT\n"); 60 printf(" /announcerelay=HOST:PORT\n");
61 printf(" /port=N (P2P listen port, default 8433)\n");
61 printf("\n"); 62 printf("\n");
62 printf("Each option also accepts '-' instead of '/'.\n"); 63 printf("Each option also accepts '-' instead of '/'.\n");
63} 64}
@@ -113,6 +114,23 @@ static void ParseStartupArguments(int argc, char* argv[])
113 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay"); 114 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay");
114 if (!announceRelay.empty()) 115 if (!announceRelay.empty())
115 strBtfAnnounceRelay = announceRelay; 116 strBtfAnnounceRelay = announceRelay;
117
118 // net.cpp has claimed nListenPort was "tunable via /port" since it was
119 // written, but nothing ever read the option. Without it a second node
120 // cannot start on a machine that already runs one -- which is exactly what
121 // testing peer exchange needs.
122 string strPort = argval2(argc, argv, "/port", "-port");
123 if (!strPort.empty())
124 {
125 int nPort = atoi(strPort.c_str());
126 if (nPort <= 0 || nPort > 65535)
127 fprintf(stderr, "Ignoring /port=%s: not a port number\n", strPort.c_str());
128 else
129 {
130 nListenPort = htons((unsigned short)nPort);
131 addrLocalHost.port = nListenPort; // or we advertise a port we never bound
132 }
133 }
116} 134}
117 135
118int main(int argc, char* argv[]) 136int main(int argc, char* argv[])
diff --git a/src/net.cpp b/src/net.cpp
index ab07f94..dfeb4a2 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -2,6 +2,13 @@
2// Distributed under the MIT/X11 software license, see the accompanying 2// Distributed under the MIT/X11 software license, see the accompanying
3// file license.txt or http://www.opensource.org/licenses/mit-license.php. 3// file license.txt or http://www.opensource.org/licenses/mit-license.php.
4 4
5#pragma push_macro("snprintf")
6#undef snprintf
7#include <nlohmann/json.hpp>
8#pragma pop_macro("snprintf")
9#ifdef snprintf
10#undef snprintf
11#endif
5#include "headers.h" 12#include "headers.h"
6#ifdef _WIN32 13#ifdef _WIN32
7#include <winsock2.h> 14#include <winsock2.h>
@@ -10,6 +17,7 @@
10#include "btfaddr.h" 17#include "btfaddr.h"
11#include "btftunnel.h" 18#include "btftunnel.h"
12 19
20void ThreadReconnectCachedBtfPeers(void* parg);
13void ThreadMessageHandler2(void* parg); 21void ThreadMessageHandler2(void* parg);
14void ThreadSocketHandler2(void* parg); 22void ThreadSocketHandler2(void* parg);
15 23
@@ -259,6 +267,269 @@ static CAddress BtfMarkerAddr(const unsigned char b5[5])
259// ConnectNodeBtfResolved (which takes an already-resolved descriptor, so it 267// ConnectNodeBtfResolved (which takes an already-resolved descriptor, so it
260// makes no Nostr relay calls at all -- see nostr.cpp's BtfResolveMany for why 268// makes no Nostr relay calls at all -- see nostr.cpp's BtfResolveMany for why
261// that matters when dialing many candidates in parallel). 269// that matters when dialing many candidates in parallel).
270//
271// Peers we have actually reached, remembered across restarts.
272//
273// Discovery is otherwise entirely dependent on the Nostr relays: a node that
274// has been running for hours and knows exactly which peers answer throws all
275// of it away on exit, and the next start walks the same ~96%-dead descriptor
276// list again. Reconnecting from this file skips the relays completely --
277// address, meeting node and encryption key are all we need.
278//
279static const size_t MAX_CACHED_BTF_PEERS = 50;
280static const int64 CACHED_BTF_PEER_TTL = 7 * 24 * 60 * 60; // a week
281
282struct CachedBtfPeer
283{
284 string btfAddr;
285 string meeting;
286 string encHex;
287 int64 lastSeen;
288 // The peer's own signed descriptor, as it announced itself. Kept verbatim
289 // because that signature is what lets us hand this peer on to somebody
290 // else: the receiver checks it against the key the address decodes to and
291 // never has to take our word for anything. Empty for entries learned
292 // before peer exchange existed, or resolved through Nostr -- those are
293 // still dialable, just not relayable.
294 string desc;
295};
296
297static string BtfPeerCachePath() { return GetAppDir() + "/btfpeers.json"; }
298
299static string BytesToHex(const unsigned char* p, size_t n)
300{
301 static const char* h = "0123456789abcdef";
302 string s;
303 s.reserve(n * 2);
304 for (size_t i = 0; i < n; i++) { s += h[p[i] >> 4]; s += h[p[i] & 0xf]; }
305 return s;
306}
307
308static bool HexToBytes(const string& s, unsigned char* out, size_t n)
309{
310 if (s.size() != n * 2) return false;
311 for (size_t i = 0; i < n; i++)
312 {
313 unsigned int b;
314 if (sscanf(s.c_str() + i * 2, "%2x", &b) != 1) return false;
315 out[i] = (unsigned char)b;
316 }
317 return true;
318}
319
320void LoadCachedBtfPeers(vector<CachedBtfPeer>& out)
321{
322 out.clear();
323 FILE* f = fopen(BtfPeerCachePath().c_str(), "r");
324 if (!f) return;
325 string body;
326 char buf[4096];
327 size_t r;
328 while ((r = fread(buf, 1, sizeof(buf), f)) > 0) body.append(buf, r);
329 fclose(f);
330 try
331 {
332 nlohmann::json arr = nlohmann::json::parse(body);
333 if (!arr.is_array()) return;
334 int64 nNow = GetTime();
335 for (const auto& o : arr)
336 {
337 if (!o.is_object()) continue;
338 if (!o.contains("btf") || !o["btf"].is_string()) continue;
339 if (!o.contains("meeting") || !o["meeting"].is_string()) continue;
340 if (!o.contains("enc") || !o["enc"].is_string()) continue;
341 CachedBtfPeer p;
342 p.btfAddr = o["btf"].get<string>();
343 p.meeting = o["meeting"].get<string>();
344 p.encHex = o["enc"].get<string>();
345 p.lastSeen = o.value("seen", (int64)0);
346 if (o.contains("desc") && o["desc"].is_string())
347 p.desc = o["desc"].get<string>();
348 if (nNow - p.lastSeen > CACHED_BTF_PEER_TTL) continue; // long gone
349 out.push_back(p);
350 }
351 }
352 catch (...)
353 {
354 LogPrint("net", "btfpeers: cache unreadable, starting empty\n");
355 out.clear();
356 }
357}
358
359static CCriticalSection cs_btfPeerCache;
360
361// strDesc is the peer's own signed descriptor when we have it (it announced
362// itself over peer exchange), "" when we only resolved it through Nostr. An
363// empty one never clears a descriptor already on file: dialing a peer we first
364// learned about by exchange must not cost us the ability to pass it on.
365static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting,
366 const unsigned char enc_pub[32],
367 const string& strDesc = string())
368{
369 CRITICAL_BLOCK(cs_btfPeerCache)
370 {
371 vector<CachedBtfPeer> peers;
372 LoadCachedBtfPeers(peers);
373
374 string strKeepDesc = strDesc;
375 for (size_t i = 0; i < peers.size(); i++)
376 if (peers[i].btfAddr == strBtfAddr)
377 {
378 if (strKeepDesc.empty())
379 strKeepDesc = peers[i].desc;
380 peers.erase(peers.begin() + i);
381 break;
382 }
383
384 CachedBtfPeer p;
385 p.btfAddr = strBtfAddr;
386 p.meeting = strMeeting;
387 p.encHex = BytesToHex(enc_pub, 32);
388 p.lastSeen = GetTime();
389 p.desc = strKeepDesc;
390 peers.insert(peers.begin(), p); // most recent first
391
392 if (peers.size() > MAX_CACHED_BTF_PEERS)
393 peers.resize(MAX_CACHED_BTF_PEERS);
394
395 nlohmann::json arr = nlohmann::json::array();
396 foreach(const CachedBtfPeer& q, peers)
397 {
398 nlohmann::json o;
399 o["btf"] = q.btfAddr;
400 o["meeting"] = q.meeting;
401 o["enc"] = q.encHex;
402 o["seen"] = q.lastSeen;
403 if (!q.desc.empty())
404 o["desc"] = q.desc;
405 arr.push_back(o);
406 }
407 FILE* f = fopen(BtfPeerCachePath().c_str(), "w");
408 if (f)
409 {
410 string s = arr.dump(2);
411 fwrite(s.c_str(), 1, s.size(), f);
412 fclose(f);
413 }
414 }
415}
416
417// Dial everything we reached last time, before the relays have said anything.
418void ThreadReconnectCachedBtfPeers(void* parg)
419{
420 vector<CachedBtfPeer> peers;
421 LoadCachedBtfPeers(peers);
422 if (peers.empty())
423 {
424 LogPrint("net", "btfpeers: no cache yet, waiting on relay discovery\n");
425 return;
426 }
427 LogPrint("net", "btfpeers: trying %zu remembered peer(s) before discovery\n",
428 peers.size());
429
430 int nConnected = 0;
431 foreach(const CachedBtfPeer& p, peers)
432 {
433 if (fShutdown) return;
434 unsigned char enc[32];
435 if (!HexToBytes(p.encHex, enc, 32)) continue;
436 if (ConnectNodeBtfResolved(p.btfAddr, p.meeting, enc))
437 {
438 nConnected++;
439 if (nConnected >= 8) break; // enough to bootstrap; the rest can wait
440 }
441 }
442 LogPrint("net", "btfpeers: %d of %zu remembered peer(s) answered\n",
443 nConnected, peers.size());
444}
445
446//
447// .btf peer exchange
448//
449// Until now discovery depended entirely on the Nostr relays: two connected
450// nodes never told each other who else existed, so a relay outage left a
451// running network unable to grow, and a node that found one stale peer had no
452// way to learn there was anything better.
453//
454// What travels here is each peer's own signed descriptor. The receiver checks
455// the Schnorr signature against the key the `.btf` address decodes to, so the
456// sender is trusted for nothing -- forging an entry would need somebody else's
457// secret key. That leaves flooding: valid descriptors for keys the sender
458// generated itself. The caps below bound one message, and the cache prefers
459// peers that actually answered, so a flood costs a Sybil more than it costs us.
460//
461
462void BtfPexCollect(vector<string>& vDescOut)
463{
464 vDescOut.clear();
465
466 // Ours first -- the node on the other end may know nobody but us.
467 string strMine = BtfLocalDescriptor();
468 if (!strMine.empty())
469 vDescOut.push_back(strMine);
470
471 vector<CachedBtfPeer> peers;
472 CRITICAL_BLOCK(cs_btfPeerCache)
473 LoadCachedBtfPeers(peers);
474
475 // Most recently seen first: these answered us, they are not names copied
476 // off a relay listing.
477 foreach(const CachedBtfPeer& p, peers)
478 {
479 if (vDescOut.size() >= MAX_PEX_DESCRIPTORS) break;
480 if (p.desc.empty()) continue; // nothing provable to pass on
481 if (p.desc.size() > MAX_PEX_DESCRIPTOR_BYTES) continue;
482 vDescOut.push_back(p.desc);
483 }
484}
485
486int BtfPexAccept(const vector<string>& vDesc)
487{
488 void* ctx = BtfSecpContext();
489 if (!ctx)
490 return 0;
491
492 string strSelf = BtfLocalAddress();
493 int nKept = 0;
494 unsigned int nSeen = 0;
495
496 foreach(const string& strDesc, vDesc)
497 {
498 if (++nSeen > MAX_PEX_DESCRIPTORS) break;
499 if (strDesc.empty() || strDesc.size() > MAX_PEX_DESCRIPTOR_BYTES) continue;
500
501 // The descriptor names the key it belongs to, and taking that key from
502 // the blob is safe precisely because the signature must verify under
503 // it: nobody can produce a valid descriptor for a key they don't hold.
504 unsigned char pubkey[32];
505 try
506 {
507 nlohmann::json v = nlohmann::json::parse(strDesc);
508 if (!v.is_object() || !v.contains("pubkey") || !v["pubkey"].is_string())
509 continue;
510 if (!HexToBytes(v["pubkey"].get<string>(), pubkey, 32))
511 continue;
512 }
513 catch (...) { continue; } // malformed JSON from an untrusted peer
514
515 btf::Descriptor d;
516 if (!btf::VerifyDescriptor(ctx, strDesc, pubkey, d))
517 continue;
518
519 string strAddr = btf::Address(pubkey);
520 if (strAddr.empty() || strAddr == strSelf)
521 continue; // ourselves, nothing to learn
522
523 unsigned char enc[32];
524 if (!HexToBytes(d.enc, enc, 32))
525 continue;
526
527 RememberBtfPeer(strAddr, d.meeting_node, enc, strDesc);
528 nKept++;
529 }
530 return nKept;
531}
532
262static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32], 533static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32],
263 const string& strMeeting, const unsigned char enc_pub[32]) 534 const string& strMeeting, const unsigned char enc_pub[32])
264{ 535{
@@ -289,6 +560,9 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p
289 if (fDebug) 560 if (fDebug)
290 LogPrint("net", "connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str()); 561 LogPrint("net", "connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str());
291 562
563 // This one answered -- worth trying first next time we start.
564 RememberBtfPeer(strBtfAddr, strMeeting, enc_pub);
565
292 // Add node 566 // Add node
293 pnode = new CNode(hSocket, addr, false); 567 pnode = new CNode(hSocket, addr, false);
294 pnode->AddRef(); 568 pnode->AddRef();
@@ -951,6 +1225,12 @@ bool StartNode(string& strError)
951 } 1225 }
952 }, 0, NULL); 1226 }, 0, NULL);
953 1227
1228 // Peers that answered last time, dialled straight away. The relays are the
1229 // only way to find anyone otherwise, and reaching them plus walking their
1230 // descriptor list is what makes a restart take minutes.
1231 if (_beginthread(ThreadReconnectCachedBtfPeers, 0, NULL) == -1)
1232 printf("Error: _beginthread(ThreadReconnectCachedBtfPeers) failed\n");
1233
954 // Peer discovery over Nostr relays (replaces the old IRC seed) 1234 // Peer discovery over Nostr relays (replaces the old IRC seed)
955 if (_beginthread(ThreadNostrSeed, 0, NULL) == -1) 1235 if (_beginthread(ThreadNostrSeed, 0, NULL) == -1)
956 printf("Error: _beginthread(ThreadNostrSeed) failed\n"); 1236 printf("Error: _beginthread(ThreadNostrSeed) failed\n");
diff --git a/src/net.h b/src/net.h
index f028c3b..7d70721 100644
--- a/src/net.h
+++ b/src/net.h
@@ -29,6 +29,21 @@ CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting
29void ThreadBtfAccept(void* parg); 29void ThreadBtfAccept(void* parg);
30void ThreadBtfConnect(void* parg); 30void ThreadBtfConnect(void* parg);
31extern string strBtfConnect; 31extern string strBtfConnect;
32
33// --- .btf peer exchange ---------------------------------------------------
34// Peers gossip signed descriptors so discovery survives a relay outage. Both
35// caps apply to a single "btfpeers" message; the generic header limit is 256 MB,
36// which is no protection at all for something a stranger can send unprompted.
37static const unsigned int MAX_PEX_DESCRIPTORS = 20;
38static const unsigned int MAX_PEX_DESCRIPTOR_BYTES = 1024;
39// Ignore a peer's exchange more often than this. One announcement per side per
40// connection is the intended traffic; anything faster is someone else's idea.
41static const int64 PEX_MIN_INTERVAL = 60;
42
43// Descriptors to hand a peer: ours first, then peers that answered us.
44void BtfPexCollect(std::vector<std::string>& vDescOut);
45// Verify descriptors a peer sent and remember the good ones. Returns how many.
46int BtfPexAccept(const std::vector<std::string>& vDesc);
32void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1); 47void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1);
33bool AnySubscribed(unsigned int nChannel); 48bool AnySubscribed(unsigned int nChannel);
34void ThreadBitcoinMiner(void* parg); 49void ThreadBitcoinMiner(void* parg);
@@ -467,6 +482,9 @@ public:
467 // publish and subscription 482 // publish and subscription
468 vector<char> vfSubscribe; 483 vector<char> vfSubscribe;
469 484
485 // Last "btfpeers" we accepted from this node, to rate-limit the exchange.
486 int64 nLastPexRecv;
487
470 488
471 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false) 489 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
472 { 490 {
@@ -483,6 +501,7 @@ public:
483 fDisconnect = false; 501 fDisconnect = false;
484 nRefCount = 0; 502 nRefCount = 0;
485 nReleaseTime = 0; 503 nReleaseTime = 0;
504 nLastPexRecv = 0;
486 vfSubscribe.assign(256, false); 505 vfSubscribe.assign(256, false);
487 506
488 // Push a version message 507 // Push a version message
diff --git a/src/nostr.cpp b/src/nostr.cpp
index ec02c2e..482a835 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -342,6 +342,36 @@ std::string BtfLocalAddress()
342 return g_nostrKey.BtfAddress(); 342 return g_nostrKey.BtfAddress();
343} 343}
344 344
345// This node's own descriptor, signed and self-certifying -- the same blob
346// PublishDescriptor puts on Nostr, but handed straight to a peer instead.
347//
348// Reads strBtfActiveRelay directly rather than going through BtfActiveRelay():
349// only a rendezvous we actually registered at is worth announcing. Handing out
350// a meeting node we are not reachable at would make every peer that receives
351// it burn a dial, and over peer exchange that mistake travels further than it
352// does over Nostr.
353std::string BtfLocalDescriptor()
354{
355 if (!EnsureNostrKey())
356 return "";
357 string meeting;
358 CRITICAL_BLOCK(cs_activeRelay)
359 meeting = strBtfActiveRelay;
360 if (meeting.empty())
361 return "";
362 return btf::SignDescriptor(g_nostrKey.ctx, g_nostrKey.seckey,
363 g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime());
364}
365
366// The shared secp256k1 context, so net.cpp can verify descriptors that arrive
367// from peers. NULL if the identity could not be loaded.
368void* BtfSecpContext()
369{
370 if (!EnsureNostrKey())
371 return NULL;
372 return (void*)g_nostrKey.ctx;
373}
374
345int GetDiscoveredPeerCount() 375int GetDiscoveredPeerCount()
346{ 376{
347 int n = 0; 377 int n = 0;
diff --git a/src/nostr.h b/src/nostr.h
index 04149e3..b5eb5e3 100644
--- a/src/nostr.h
+++ b/src/nostr.h
@@ -82,6 +82,14 @@ bool BtfGetIdentity(unsigned char pubkey[32], unsigned char enc_sk[32]);
82// This node's printable .btf address, or "" if the identity failed to load. 82// This node's printable .btf address, or "" if the identity failed to load.
83std::string BtfLocalAddress(); 83std::string BtfLocalAddress();
84 84
85// This node's own signed descriptor, ready to hand to a peer over the wire.
86// "" until ThreadBtfAccept has registered at a rendezvous.
87std::string BtfLocalDescriptor();
88
89// The shared secp256k1 context, for btf::VerifyDescriptor (which takes it as
90// void* to keep headers free of secp). NULL if the identity failed to load.
91void* BtfSecpContext();
92
85// Resolve a .btf address via the public discovery relays: fetch the owner's 93// Resolve a .btf address via the public discovery relays: fetch the owner's
86// self-certified descriptor and return its meeting node ("host:port") and 94// self-certified descriptor and return its meeting node ("host:port") and
87// x25519 public key for the end-to-end channel. 95// x25519 public key for the end-to-end channel.