upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.cpp31
-rw-r--r--src/net.cpp115
-rw-r--r--src/net.h19
-rw-r--r--src/nostr.cpp30
-rw-r--r--src/nostr.h8
5 files changed, 201 insertions, 2 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/net.cpp b/src/net.cpp
index b5d12d0..1d552cf 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -285,6 +285,13 @@ struct CachedBtfPeer
285 string meeting; 285 string meeting;
286 string encHex; 286 string encHex;
287 int64 lastSeen; 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;
288}; 295};
289 296
290static string BtfPeerCachePath() { return GetAppDir() + "/btfpeers.json"; } 297static string BtfPeerCachePath() { return GetAppDir() + "/btfpeers.json"; }
@@ -336,6 +343,8 @@ void LoadCachedBtfPeers(vector<CachedBtfPeer>& out)
336 p.meeting = o["meeting"].get<string>(); 343 p.meeting = o["meeting"].get<string>();
337 p.encHex = o["enc"].get<string>(); 344 p.encHex = o["enc"].get<string>();
338 p.lastSeen = o.value("seen", (int64)0); 345 p.lastSeen = o.value("seen", (int64)0);
346 if (o.contains("desc") && o["desc"].is_string())
347 p.desc = o["desc"].get<string>();
339 if (nNow - p.lastSeen > CACHED_BTF_PEER_TTL) continue; // long gone 348 if (nNow - p.lastSeen > CACHED_BTF_PEER_TTL) continue; // long gone
340 out.push_back(p); 349 out.push_back(p);
341 } 350 }
@@ -349,22 +358,35 @@ void LoadCachedBtfPeers(vector<CachedBtfPeer>& out)
349 358
350static CCriticalSection cs_btfPeerCache; 359static CCriticalSection cs_btfPeerCache;
351 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.
352static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting, 365static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting,
353 const unsigned char enc_pub[32]) 366 const unsigned char enc_pub[32],
367 const string& strDesc = string())
354{ 368{
355 CRITICAL_BLOCK(cs_btfPeerCache) 369 CRITICAL_BLOCK(cs_btfPeerCache)
356 { 370 {
357 vector<CachedBtfPeer> peers; 371 vector<CachedBtfPeer> peers;
358 LoadCachedBtfPeers(peers); 372 LoadCachedBtfPeers(peers);
359 373
374 string strKeepDesc = strDesc;
360 for (size_t i = 0; i < peers.size(); i++) 375 for (size_t i = 0; i < peers.size(); i++)
361 if (peers[i].btfAddr == strBtfAddr) { peers.erase(peers.begin() + i); break; } 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 }
362 383
363 CachedBtfPeer p; 384 CachedBtfPeer p;
364 p.btfAddr = strBtfAddr; 385 p.btfAddr = strBtfAddr;
365 p.meeting = strMeeting; 386 p.meeting = strMeeting;
366 p.encHex = BytesToHex(enc_pub, 32); 387 p.encHex = BytesToHex(enc_pub, 32);
367 p.lastSeen = GetTime(); 388 p.lastSeen = GetTime();
389 p.desc = strKeepDesc;
368 peers.insert(peers.begin(), p); // most recent first 390 peers.insert(peers.begin(), p); // most recent first
369 391
370 if (peers.size() > MAX_CACHED_BTF_PEERS) 392 if (peers.size() > MAX_CACHED_BTF_PEERS)
@@ -378,6 +400,8 @@ static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting,
378 o["meeting"] = q.meeting; 400 o["meeting"] = q.meeting;
379 o["enc"] = q.encHex; 401 o["enc"] = q.encHex;
380 o["seen"] = q.lastSeen; 402 o["seen"] = q.lastSeen;
403 if (!q.desc.empty())
404 o["desc"] = q.desc;
381 arr.push_back(o); 405 arr.push_back(o);
382 } 406 }
383 FILE* f = fopen(BtfPeerCachePath().c_str(), "w"); 407 FILE* f = fopen(BtfPeerCachePath().c_str(), "w");
@@ -419,6 +443,93 @@ void ThreadReconnectCachedBtfPeers(void* parg)
419 nConnected, peers.size()); 443 nConnected, peers.size());
420} 444}
421 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
422static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32], 533static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32],
423 const string& strMeeting, const unsigned char enc_pub[32]) 534 const string& strMeeting, const unsigned char enc_pub[32])
424{ 535{
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.