net: exchange signed .btf descriptors between peers - #32
Merged
Conversation
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.
Bitflash-sh
added a commit
that referenced
this pull request
Jul 29, 2026
Everything since 1.2.2 is about nodes being able to find and reach each other, which is the problem this project actually had: #30 rendezvous registration and waiting split apart. Registering used to block until somebody dialled, so a node could not advertise a meeting node until it had already been reached at one. This is the root cause of the flaky connectivity, not dead relays or stale descriptors. #26 a node no longer advertises a rendezvous it is not registered at #25 .btf peers that answered are remembered and dialled first (36.3s -> 2.1s) #32 peers exchange signed descriptors, so discovery survives a relay outage #29 debug.log stopped dropping lines under concurrency #28 /port, so two nodes can share a machine
Vic-Nas
added a commit
to Vic-Nas/bitflash-mod
that referenced
this pull request
Jul 29, 2026
…-sh#32) 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. 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. Per-message caps (MAX_PEX_DESCRIPTORS, MAX_PEX_DESCRIPTOR_BYTES) and a per-connection rate limit (PEX_MIN_INTERVAL via CNode::nLastPexRecv) bound flooding with self-generated keys, which signatures alone don't prevent. Unknown commands are already ignored, so a node without this just drops the message -- not a protocol break. Adapted rather than cherry-picked, same as the other Bitflash-sh#25/Bitflash-sh#28/Bitflash-sh#29/Bitflash-sh#30 ports: our net.cpp diverged (CachedBtfPeer/RememberBtfPeer already exist here from the peer-cache port, and use our own BtfPeerCacheHexToBytes/BytesToHex naming rather than generic HexToBytes/BytesToHex to avoid symbol collisions elsewhere in this codebase). CachedBtfPeer gains a `desc` field -- the peer's own descriptor kept verbatim, since that signature is what makes a peer relayable to someone else. RememberBtfPeer takes it as an optional arg and never lets a plain dial (empty desc) clear one already on file, so learning a peer through exchange doesn't get undone the next time we happen to also reach them directly. nostr.cpp gains BtfLocalDescriptor() (reads strBtfActiveRelay directly, not through BtfActiveRelay()'s fallback -- only a rendezvous we've actually confirmed registration at is worth handing to a peer) and BtfSecpContext() (so net.cpp can verify without pulling secp256k1 into net.h).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
addrgossip went out with the IRC seed and nothingreplaced it.
What travels
Each peer's own signed descriptor — the same self-certifying blob that goes on
Nostr, handed straight over a new
btfpeersmessage. The receiver checks theSchnorr signature against the key the
.btfaddress decodes to, so the senderis trusted for nothing: forging an entry needs somebody else's secret key.
That leaves flooding with self-generated keys, which is bounded rather than
prevented: 20 descriptors per message, 1 KB each, one exchange per peer per
minute, and the cache prefers peers that actually answered over names heard
about. A Sybil can spend keys to fill a slot list; it cannot impersonate anyone.
Unknown commands are ignored (
main.cpp), so nodes without this drop the message.Nothing here is a protocol break.
Why the descriptor is stored
The peer cache from #25 keeps parsed fields, which cannot be relayed — passing
those on would mean asking someone to trust us. The cache now also keeps the
signed blob verbatim, populated when a peer announces itself. Entries without one
(resolved through Nostr, or written before this) stay dialable, just not
relayable, and a dial never clears a descriptor already on file.
Tested
Two nodes on one machine, B dialing A by
.btfaddress, real relays:Both directions verified and persisted. This needed #28 to run two nodes on one
machine, #29 to trust the log, and #30 to get a node advertised at all — the same
test failed twice earlier today for those reasons rather than anything here.
Build green on MSYS2 UCRT64. No consensus, wallet or block-handling code is
touched.
Note on history
An earlier version of this rode into main on #25's branch without review and was
removed by #27. This is that work, rebased and now with the test it was missing.