BIP324 Proxy: easy integration of v2 transport protocol for light clients (PoC)

Hi,

I’ve been working on a small tool for enabling peer-to-peer encryption for bitcoin clients that haven’t implemented BIP324 yet.

The basic idea is to run a local process with the sole purpose of translating between p2p v1 and v2 protocols. The proxy starts a server socket on local TCP port 1324 [1] and spins up a new thread for every incoming v1 connection. The remote peer address is determined from the incoming first VERSION message which conveniently contains this information in the addr_recv field (see Protocol documentation - Bitcoin Wiki). After performing the v2 handshake [2], the previously received VERSION message is sent to the remote node and subsequently, the proxy fowards incoming messages from either side to the opposite side, translating it to the corresponding p2p version format.

To take use of BIP324 proxy, all a light client has to do is to initiate all outbound P2P connections to localhost:1324 instead of the actual remote peer address on the TCP level. This usually doesn’t need more than a single-line patch if hardcoded, though ideally a client should provide this as a command-line option (e.g. -bip324proxy=…).

The implementation is written in Python3 without any external dependencies. Most of the code, especially the cryptographic primitives, are not by me, but taken from Bitcoin Core’s BIP324 implementation in the functional test framework (Kudos to stratospher and sipa). Note that this project is still in proof-of-concept phase, terribly slow and vulnerable to side-channel attacks. It’s not recommended to use it for anything but tests right now. To reflect that, only signet is supported (though it’s trivial to enable other networks by changing the NET_MAGIC constant).

The plan is to do an efficient rewrite in Rust. As I’m not fluent in this language, this could take a while. Here are the links to the Github repository and a presentation that I held recently in a Brink engineering call:

The slides contain a few examples of light clients that I’ve tested the BIP324 proxy with, showing patches that are needed for redirection to the proxy.

Suggestions and ideas are very welcome.

Cheers, Sebastian

[1] Note that the port 324 would have been a nice choice reflecting the BIP number, but TCP/IP ports below 1024 need superuser privileges at least on Unix-like operating systems, hence I added a “1” (standing for “v1”) in front. 1324 is not taken yet, according to some well-known port lists that I checked, so it shouldn’t collide with other local services and I think it’s still a nice, somewhat easy-to-remember choice.

[2] Note that reconnection with v1 (if the v2 handshake fails) is implemented, but currently hardcoded to be disabled, in order to keep the full promise of a “BIP324 proxy”, i.e. only v2 connections are allowed. As v2 support is still not widespread on listening nodes, it’s probably not a good idea to use this yet, as one could end up without any connection.

10 Likes

Cool project!

There are P2P clients that don’t put their address in addr_recv. They put, e.g. 127.0.0.1, 0.0.0.0, or whatever in there. Would this be a problem?

I haven’t seen anyone working on a BIP324 implementation in Rust. Might fit well in the excellent rust-bitcoin library or some other place in their ecosystem (e.g. rust-bitcoin/bip324).

3 Likes

Oh, that’s good to know, I wasn’t aware. If the VERSION’s addr_recv field doesn’t contain the real address of the remote node, that would indeed be a problem, as this is the only way for the proxy to know where to initiate the v2 connection to. If set to an arbitrary value, the connection would then very likely fail (or connect to a different peer than intended, which is probably even worse).

Do you know of any concrete P2P clients that follow this practice (or is it more like connections with obscure user agents that can’t be tied to a concrete implementation)? I might add a prerequisites section to README.md mentioning the reliance on addr_recv being set correctly, together with a list of clients that are already known to be incompatible with BIP324 proxy.

Yeah, I also thought that putting some parts of BIP324 to a library might be a good idea. Will for sure take a deeper look at rust-bitcoin at some point in the course of my upcoming Rust journey.

1 Like

I briefly looked into it and it seems like all inbound LinkingLion connections set 127.0.0.1 and inbound i2p and tor (presumably) Bitcoin Core connections set 0.0.0.0.

Based on the “BIP324 proxy scenario” graphic and the slides you linked, I noticed that the proxy is only for outbound connections for now, correct? You mention “Investigate inbound connections support via reverse proxy” as TODO. I incorrectly assumed you’re implementing in and outbound. For outbound only, it should be fine.

However, only version 1 address serialization is possible in (inbound/outbound) version messages. BIP-155 address serialization of e.g. TorV3, I2P, CJDNS isn’t possible. These will always be 0.0.0.0 (bitcoin/src/net_processing.cpp at e1ce5b8ae9124717c00dca71a5c5b43a7f5ad177 · bitcoin/bitcoin · GitHub).

Thanks for taking a deeper look! :ok_hand:

Yes, it only works for outbound connections for now. The reverse proxy idea seemed interesting to me from a technical perspective, but thinking more about it, it’s probably not that useful in practice. There is already the possibility to run a listening node with BIP324 now (by running Bitcoin Core v26.0+), and I’d assume that relevant alternative node implementations implement it soon (at least significantly earlier than most light clients).

Ok, that’s good news.

Good point. I haven’t really checked how the proxy idea would work together with any of these protocols. I guess it just doesn’t, but intuitively I would say it’s fine if that’s not supported, as these protocols already offer encryption on another layer anyways.

1 Like

Really cool!

I haven’t looked into how you have the tool designed, but any interest in making this a rust library? For example, GitHub - cloudhead/nakamoto: Privacy-preserving Bitcoin light-client implementation in Rust has it on their roadmap to add BIP324, and I’m sure there are others. Seems like having a library would be generally useful and something that your proxy could be built around.

I’m also not fluent in Rust but learning and looking for small-ish projects to work on, so I’d be more than happy to help work on a BIP324 rust library if you’re interested in collaborating.

Yup, I agree that creating a BIP324 library makes a lot of sense (see also 0xb10c’s post above where one idea is to add it to rust-bitcoin).

Sure, that would be great! I forgot to mention this explicitly in the OP, but of course everyone is more than welcome to contribute. For starters, it should be pretty straight-forward to create a module for the BIP324 cipher suite. I think we can use the same interface as BIP324Cipher in Bitcoin Core (see src/bip324.{h,cpp}). Given that there are (hopefully) already Rust crates available for efficient implementations of the cryptographic primitives (secp256k1-ellswift bindings, ChaCha20(Poly1305), HKDF-SHA256 etc.), this shouldn’t even be too much code.

1 Like

Hey, super cool project. I am happy to see other people excited about BIP324 and building tools to drive p2p towards v2. A friend and I have been working on a Rust implementation that will act as an API for clients to place into their pre-existing TCP logic, and we would appreciate some further guidance/contribution. Our main priority has been removing dependencies, and we are now down to just a few from rust-bitcoin. My main concern now is building the top level API around the Floresta Rust client, and I will be working on that this week. Our code is here.

Nice, that’s great to hear and comes at the perfect point of time! I’m sure there will be some further questions / suggestions when it comes to the details of the API, but at a first glance this seems like exactly what we need for BIP324 Proxy. Just being curious, what was the motivation to reduce the cryptography dependencies and reimplement them? Do the available Rust crates have any significant drawbacks, or is that more a generic philosophical decision of the library? I’m not saying it’s a bad idea (we actually do the same in Bitcoin Core), I just assumed that in Rust the package management works well enough, and I think I wouldn’t have a problem depending on a well-maintained cryptographic library, which ideally has the resources to investigate optimizations etc.

As a small update, I’ve started the Rust rewrite branch yesterday: GitHub - theStack/bip324-proxy at rust_rewrite It’s still tiny, so far it only creates the local server socket and displays a mesage for an incoming client connection, without spinning up a new thread yet. The plan would be to implement a dummy proxy (v1<->v1) first and only then plug in all the BIP324 related stuff. Let’s see how that goes. As always, contributions in any form are welcome (even if it’s recommended book material / common pitfalls for network programming in Rust or whatever).

1 Like

Perfect, will keep a pulse on your src. My favorite Rust book is Effective Rust, and the tokio docs have good async TCP examples. On cryptography, the RustCrypto collection of crates contain unsafe code blocks and are not particularly auditable. I have been in touch with the rust-bitcoin maintainers, and they recommended the dependency reduction as a step towards being merged to their community, presumably so the crypto is concise and readable. I am also working on this project as part of a Chaincode program, and I think any additional “proof of work” I can put out will hopefully put my name out as a FOSS dev! If you would like to join forces, you can create a main in our src and implement the proxy logic there as well. I might also fork yours and see if I can build up a proxy with my crate. Cheers

2 Likes

Hey @theStack , @rustaceanrob and I have been hacking along in our BIP324 repo and have a working rust-based v2 proxy (heavily influenced by your Python version) I thought you might find interesting! It is async, using the Tokio runtime: bip324/proxy/src/bin/async.rs at fae064c22f77c62e3541cceea33690ef3efad52b · rustaceanrob/bip324 · GitHub

I am also going to be working on a threaded implementation since this has been a pretty useful exercise to help inform what type of interface we want our bip324 protocol library to expose. We have a bit more to clean up and iterate on there, but have generally adopted a “sans I/O” approach to keep it runtime agnostic.

2 Likes

@yonson: That’s very cool, thanks for working on that! I’m looking forward to try it out later and do some manual testing with clients that I’ve tried bip324-proxy with before (i.e. primarily nakamoto, neutrino, bcoin in SPV mode, and last but not least, Bitcoin Core). Also happy to hear that the project helps shaping the interface for the bip324 library.

I am also going to be working on a threaded implementation since this has been a pretty useful exercise to help inform what type of interface we want our bip324 protocol library to expose. We have a bit more to clean up and iterate on there, but have generally adopted a “sans I/O” approach to keep it runtime agnostic.

Can you elaborate in what aspects the threaded implementation will differ from the current one? To my understanding, a thread is already spawned for each incoming v1 connection (otherwise, only one connection would be supported).

Can you elaborate in what aspects the threaded implementation will differ from the current one? To my understanding, a thread is already spawned for each incoming v1 connection (otherwise, only one connection would be supported).

Sure! The async implementation is “spawning” new threads of work, but these are “green threads” not tied 1:1 with operating system threads.

My use of “threaded implementation” is a bit vague with all this concurrency talk, but I specifically mean an operating system thread implementation. The big benefit of this is that we could then code up standard library based wrappers which work against the std::io::Read/Write traits (these generally don’t play nice in async-land). The cost of the OS thread impl simplicity is the standard heavy resource usage per connection.

1 Like

@theStack thank you for sharing your bip324 proxy work, looks very interesting. I started coding a Golang version and your repo has been very helpful, I hope to share the first version soon and also make available the bip324 implementation as a library.

One question: your approach relies on patching the existing clients which could be hurdle to usage. I had one idea to let the user configure proxy with peer1, peer2, peer3, etc and then listen on port 1 which proxies msgs to peer1, port 2 which proxies to peer 2 and so on. In that way the bitcoin software (for example btcd or anything really) can configure localhost:port1 and localhost:port2 as peers and then proxy will send bip324 msgs to peer1 and peer2. No patching of software. One downside is that peer discovery would need to move to proxy or peer config is static but it could work with dns-seed to discover peers on the fly.

Proxy would work like this:

./bip324-proxy --peers=a.b.c.d:8333,e.f.g.h:8333,m.n.o.p:8333
proxy to a.b.c.d:8333 listening on 127.0.0.1:38401
proxy to e.f.g.h:8333 listening on 127.0.0.1:38402
...
Proxy server listening on 127.0.0.1:38400

Maybe that makes it more usable? What do you think?

@Liz.Lightning

@theStack thank you for sharing your bip324 proxy work, looks very interesting. I started coding a Golang version and your repo has been very helpful, I hope to share the first version soon and also make available the bip324 implementation as a library.

Nice, glad to hear that the idea is picked up and looking forward to see an implementation of bip324-proxy in another language soon! I’m curious how advanced the golang ecosystem is w.r.t. the needed cryptographic primitives (e.g., are there even proper secp256k1 bindings available for doing the EllSwift pubkey encoding? I’m aware that btcd uses a custom secp256k1 library from the decred project, as I just stumbled upon that recently, but I don’t know if it is up-to-date and how performant and well-written it is).

One question: your approach relies on patching the existing clients which could be hurdle to usage. I had one idea to let the user configure proxy with peer1, peer2, peer3, etc and then listen on port 1 which proxies msgs to peer1, port 2 which proxies to peer 2 and so on. In that way the bitcoin software (for example btcd or anything really) can configure localhost:port1 and localhost:port2 as peers and then proxy will send bip324 msgs to peer1 and peer2. No patching of software. One downside is that peer discovery would need to move to proxy or peer config is static but it could work with dns-seed to discover peers on the fly.

Interesting idea, haven’t thought about doing static configuration yet. Not needing to patch the light client is a plus of course, but I imagine this approach also could have some problems:

  • do light clients allow to add multiple peers with the same IP address (even if the port is different)? Note that the proposed light client patches change the destination address at a low socket level, so the higher-layer logic of the client doesn’t even notice that there is a proxy in-between. If there are checks in the code that e.g. don’t allow to add local addresses or multiple addresses with the same IP, that could be a problem.
  • how would a user know which remote peers to configure? Every light client might have slightly different requirements for peers in terms of required services, so discovering peers that fit the used client could be non-trivial.

That said, I think it’s worth it to try it out and experiment with that idea.

I looked for libraries (including the decred library you linked) and did not see something well working so I went with the basic approach and translated the reference.py implementation to Golang. It’s ugly but works :slight_smile: I’ll look at the decred library again to see if I can utilize it.

I don’t think this will be a grave problem, the client should use “ip:port” to identify clients, not just ip address but to be honest I don’t know for sure. I mostly just tested with btcd as I had trouble getting a simple neutrino client running - do you have a minimal example please so I could test?

You are right, I don’t have good answers for that. You could just use the dns seed peers and pick at random but it’s not guaranteed to be a good choice.

It took a while longer than thought but I finally got around to make public the initial version of the proxy:

It is usable and works but there’s a lot of room for improvement. Please share feedback or ideas for improvements or other comments.

@Liz.Lightning: Very nice. I did a quick test starting the proxy via ./bitcoin-bip324-proxy -network signet -v2-only and using Bitcoin Core to do a signet IBD (with a patch initiating connections to the proxy, similar to the slides). Worked without any problems. :ok_hand:

Will take a deeper look soon, also curious about the code, so far I have only one point for feedback: I think the port 38333 as default for the proxy is not a good choice, as it’s also the default listening port for signet. I’d go for something that is distinct and doesn’t collide with anything existing (e.g. I chose 1324 for the Python bip324-proxy).

Good point about the port, I changed it to 8324

Good to hear your first test worked, looking forward to more feedback.

My next step is to refactor the code and use the btcd packages to send and receive v1 messages. I also want to see if I can hack btcd to make v2 connections using the code I already have.