Show HN: Peer-to-Peer data transfer tool based on libp2p
github.com
github.com
Is there any decentralized p2p network where it is easy to find content nowadays?
We're so close as well! P2P is stronger than ever, especially torrents who have very mature implementations now. We're just missing being able to distribute the metadata in order to get the torrents (or magnet links) to be P2P as well, something like TPB on a P2P network would make things unstoppable.
I think magnetico is not expected to replace a proper DHT node but be run in parallel: one is naturally present because you're a peer in any amount of torrents, the other is using the network for searching. I'd expect both to be running on the same machine.
After a few weeks of running it I know have a 2.6GiB database of torrents. I wouldn't expect a standard torrent node to include this feature, especially because the value of magnetico only comes when the database is big enough.
That said magnetico should definitely be a standard node in the network, it's light enough that it can do it
Maybe in the future. As things are right now, magnetico is a huge bandwidth eater and will literally eat all available bandwidth if it can, doesn't seem to have any limits setup today so if you want to be able to do other things at the same time, you have to limit the process/IO manually.
Do you know if anyone has explored the viability of smaller-scale file sharing? As in I make my content available directly to my friends, and they can search over my and their friends content? Privacy leads to better security as there is potentially no central server or network to compromise...
But the vast influx of people that use smart phones as their main system means most don't have full ISP and real connection to the internet. At best they might have an ipv6 address. But an ipv6 and CNAT ipv4 still doesn't allow you to communicate with most peers on the internet even if it does work for most corporate websites.
If ipv6 ever arives then p2p can work. But until that time most mobile devices cannot participate in most of the internet, p2p included. And mobile is where the money is spent.
What's the PAKE used for, if the public key is already verified? What's the "password" here?
As lotharrr mentioned other flaws, you may want to check out his suggestion which seems like the way to go for me. This to eliminates this "sanity check" step all together.
The more different things use libp2p, the more flexible it will become. We can get meaningful experimentation and interoperability (different concepts won't interoperate of course, but the same concept will). This is nice!
The sending peer runs:
$ pcp send my_file
Code is: bubble-enemy-result-increase
On the other machine run:
pcp receive bubble-enemy-result-increase
The receiving peer runs:
$ pcp receive december-iron-old-increase
Looking for peer december-iron-old-increase...Nice! I'm glad to see libp2p getting more use. I would love to have it as a connection protocol in magic-wormhole someday. Your implementation looks really slick.
I'll echo mintplant's question.. I'm interested in more details of the protocol. From the README (which is really good, thank you), I gather that it creates a libp2p keypair, uses the first 44 bits of the pubkey as the transfer code, use the first 8 bits plus a rounded timestamp as a DHT channel ID, then.. publishes its libp2p contact information to a DHT slot under the channel ID and waits for the peer to connect? I guess I need to learn more about libp2p and the difference between publishing information to a key, and accepting connections. Like mintplant, my big question is what gets used as the PAKE secret.
The PAKE step in magic-wormhole is there to make sure there's some secret value that is known only to the two correspondents, not anything in the middle, and use that to negotiate a full session key. It's the only piece of knowledge that distinguishes your intended recipient from an attacker.
If you're using the 44 bit transfer code as the PAKE "secret", and that's trivially derivable from the pubkey, and the pubkey is observable to anyone watching the network (or the DHT entries), then it's not actually a secret. An attacker could monitor the DHT for new keys in the "/pcp/" namespace, read their contents, connect to the indicated sender, note the public key they used for the connection, rebuild the transfer code that you created, run the protocol in the same way your intended recipient would, and steal the file. The sender would see the program complete earlier than they expected (before they even told anyone the transfer code), and the recipient would see some sort of error.
A particularly clever attacker would read the sender's data from the DHT, create a keypair with the same first 44 bits (requires 2^44 keypairs, but that's not comfortably impossible, and all of the work could be done ahead of time), flood the DHT with their own details (so the recipient connects to the attacker's host instead of yours), and then man-in-the-middle the connection, allowing them to both steal your file and substitute an alternate one to your recipient, with minimal evidence left behind.
But there's an easy fix: have it create four random words, use the first one or two as the channel ID (perhaps combined with the quantized timestamp), and the remainder as the secret PAKE input, which would completely solve that problem. The channel ID could be derived from a randomly-generated libp2p pubkey, or it could just be random. The important thing is that the "password" input to the PAKE step is uniformly random and unrelated to anything outside of the transfer code, and that it never gets revealed to anyone but the recipient (so it can't be used for any network purposes).
Cool stuff.. thank you for building it!
> [...] it creates a libp2p keypair, uses the first 44 bits of the pubkey as the transfer code, use the first 8 bits plus a rounded timestamp as a DHT channel ID, then.. publishes its libp2p contact information to a DHT slot under the channel ID and waits for the peer to connect?
That summarizes it really well. There are actually three types of records you can store in the DHT of IPFS: Provider, IPNS and Peer records [0]. What pcp stores in the DHT are:
- cid("/pcp/{unixts}/chanID") -> peerID (provider record) - peerID -> multiaddresses (peer record)
where cid stands for the content ID [1] of the given string. So the receiving peer first searches for cid("/pcp/{unixts}/chanID"), finds the peerID and then searches for its associated addresses. These addresses can contain the public address of your home router (if NAT traversal is possible) or relay addresses.
All of this complexity is pretty much hidden away by libp2p, which is amazing!
Regarding your points about using PAKE/Public Key etc: I thought of making it even harder by tying the random code to the identity - but this is indeed flawed. I'll change the logic to your suggestion!
Honestly, thanks for your write-up and the valuable input!
[0] https://docs.ipfs.io/concepts/dht/ [1] https://docs.ipfs.io/concepts/content-addressing/
When a p2p application starts the first time it needs to know how to contact the first peer. After that the first peer can give addresses of more peers. But how can the p2p application get the address of the first peer?
A solution would be a central server that maintains a list of peers. But this is not p2p. After all the point of p2p is that it doesn't need central servers.
https://github.com/dennis-tra/pcp#how-does-it-work uses IPFS to contact the first peer. OK.
However in the quest to understand how to boostrap p2p really independent of a central service, I feel somewhat cheated, because IPFS also would need to bootstrap. It's sort of kicking the can down the road. How does IPFS bootstrap?
Is there really a way to bootstrap p2p without reliance on a list of central servers?
I thought about a hardcoded algorithmic series of addresses. Let's say by using a repeatable random generator. An example: the first address is jec6r5bz.io, then 0ueasze6.ch, etc., I made up the addresses. The project would register the first few addresses and if they are blocked, register more addresses on that series. This way the application can bootstrap independent of a specific central server because it can try the next address if it doesn't work.
I've been thinking about finding simple solutions to the problem like treating the IP space like a binary search space or generally finding solutions that are better than random sampling (More methods are compared in https://link.springer.com/content/pdf/10.1007%2F978-3-642-01...)
I anyone can recommend more resources for learning about bootstrapping methods or has examples of how other apps solve the problem, please let me/us know!
Yes. A list of nodes. The key part is that for p2p bootstrap you don't need to learn about any special node in the network, just any node will do. A set of initial, long-lived contacts could be embedded in a link when sharing content , it can be loaded from a file, it can be obtained via configurable DNS lookups. If your network were so niche that you couldn't even afford a server and all you have is an anonymous internet forum then you could even post a pastebin with some IPs and ports every now and then and prompt users to paste that.
And once you have bootstrapped yourself you can keep a local cache of long-lived nodes which you can also provide to others.
It seems philosophically hard, but practically speaking you only need to get in once and there are so many ways to do that. A well-known, server-like points of contact are mostly used because they are the most convenient and reliable option, but they're not really the only option.
For example if you download some file then you need to exchange some content-identifying token anyway, a magnet link in bittorrent. That link can also contain a bunch of nodes the source peer knows, preferably the longest-lived ones it knows. Depending on where it's used you could also turn this into a QR-code, NFC peer or whatever.
Practically speaking an implementation will already contain a bootstrap list or might download it from somewhere else on first start. But that's just for convenience.
IPFS bootstraps by either having a static list of bootstrap peers (essentially normal nodes in the network) that are run by trusted organizations (currently some of them are run by Protocol Labs who also develop IPFS, libp2p and some other software), which is similar to how many P2P networks does bootstrapping. Or, if there is any nodes on the local network, those can also be discovered via mDNS that allows discovery on local networks. Once you've found one peer, you can exchange "address books" and find more peers.
Other ways, that I don't think are implemented anywhere in the wild as far as I know, is to do some sort of random-walk of the IP address space and just try to connect to random hosts, see if they are nodes. Obviously not super cheap, especially compared to bootstrap lists, and introduces some issues if the protocol is normally run on random ports and/or if NAT is being a particular bitch for the protocol to work around, which it tends to be. Coupled together with a local cache of previously seen nodes, it'll make initial bootstrap a bit slow but very resilient, and future disconnect/reconnects faster, but at least it'll be fully decentralized.
One could also imagine with the proliferation of new P2P technology like blockchains and eventually Matrix moving to P2P, that you might be able to use those for bootstrapping as well. If someone runs a Ethereum node with some Ether, maybe providing bootstrapping functionality for other networks could be something that could work via smart contracts? Just spit-balling here, but more out-of-band channels could be added for the discovery of peers.
Edit: a free-time curiosity of mine has always been to use inaudible sound for humans as a discovery protocol as well, but haven't really looked into that too much. If the nodes could use the microphone to pickup bytes from broadcasting nodes via audio, one might be able to put speakers in cities that aren't really noticeable for humans (but maybe too much for animals/other tech?) that allows nodes to bootstrap via it.
I really appreciate that this project gets a little attention.
Libp2p provides ways to establish a connection through NATs. For more information I recommend looking up the Identify [0] and Circuit Relay [1] protocols, as well as the NAT Traversal docs [2].
I implemented time based sharding because the DHT entries will remain there for up to 24h. So there is a significant chance for a channel ID collision that would result in connection attempts to peers that are long gone.
This is indeed a bit unergonomic and I haven’t covered edge cases that come with that approach. Open for suggestions :)
Best
[0] https://docs.libp2p.io/concepts/protocols/#identify [1] https://docs.libp2p.io/concepts/protocols/#circuit-relay [2] https://docs.libp2p.io/concepts/nat/
Based on a strict reading of the readme you actually need to receive in the same 5min window. This means that you have at most 5min. If you send at the end of the window you can have less than a second.
A simple solution is to have the sender always round up (to the end of the current 5min window) and have the receiver search for windows rounded up and down. (Or have the sender broadcast two and the receiver look for one)
If you want more time the sender can keep broadcasting a new window every 5min until the receiver connects.
> I haven’t covered edge cases
So, thanks for your suggested solution! I think that's how I would implement it as well.
The receiver looks for two windows and the sender broadcasts new entries when 5min are over.
Anyone here using pcp, croc, magic-wormhole, etc.?
What are you using it for?
Sick project, btw.
I think the APIs to have simple, sequential request/response communication with a peer could be easier. How to close communication and be sure data was received by the peer was also a tricky one. Just because you have written the data to the libp2p stream doesn't mean it was transferred to your peer. So I was continuously closing the stream too early, which resulted in data loss. Took me a little while to figure this one out.
I can recommend having a look at the examples [1] if you're planning to build your project in Go. They helped me a lot to understand how the APIs are supposed to be used. At a few points there outdated though - especially how to close streams (I just saw the examples were updated 2 days ago, so this is probably an outdated statement).
[0] https://docs.libp2p.io/concepts/ [1] https://github.com/libp2p/go-libp2p-examples