Skip to content

net: exchange signed .btf descriptors between peers - #32

Merged
Bitflash-sh merged 1 commit into
mainfrom
feat/btf-pex
Jul 29, 2026
Merged

net: exchange signed .btf descriptors between peers#32
Bitflash-sh merged 1 commit into
mainfrom
feat/btf-pex

Conversation

@Bitflash-sh

Copy link
Copy Markdown
Owner

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 addr gossip went out with the IRC seed and nothing
replaced it.

What travels

Each peer's own signed descriptor — the same self-certifying blob that goes on
Nostr, handed straight over a new btfpeers message. The receiver checks the
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 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 .btf address, real relays:

A = wioga2dg7yjgb7b4cb5phmretc4q7dw64xlgpkak7tstnioc23v6nzq.btf
[nostr] .btf self-resolve OK via wss://nos.lol (meeting_node=31.44.4.249:8434, ...)
B conectou em A : SIM
A: btfpeers: kept 1 of 1 descriptor(s)
B: btfpeers: kept 1 of 1 descriptor(s)
cache A: 1 peer, 1 with a signed descriptor
cache B: 1 peer, 1 with a signed descriptor

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.

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
Bitflash-sh merged commit 13e8ef5 into main Jul 29, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant