skypeernet is a decentralized peer-to-peer file sharing network with blockchain-based file tracking.
- Features
- Architecture
- Building
- Quick Start
- Configuration
- CLI Commands Reference
- File Sharing Workflow
- TCP Tunneling
- Plugins
- Troubleshooting
- Decentralized P2P Network: Direct peer-to-peer communication with tracker-assisted discovery
- Blockchain-based File Index: Files are tracked in a mineable blockchain
- Chunked File Transfer: Large files are split into chunks with SHA256 verification
- RSA Authentication: Peers authenticate using RSA key pairs
- NAT Traversal: Proxy/shadow routing for peers behind NAT
- TCP Tunneling: Tunnel TCP connections over the P2P network
- Bandwidth Control: Configurable upload/download limits
- Plugin System: Erlang (johnconnor) and Python (skynet) plugins
┌─────────────────────────────────────────────────────────────────┐
│ NETWORK │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Tracker │◄──►│ Peer A │◄──►│ Peer B │◄──►│ Peer C │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ UDP Protocol (port 5775) │
└─────────────────────────────────────────────────────────────────┘
PEER INTERNALS:
┌─────────────────────────────────────────┐
│ Application │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ CLI (stdin)│ │ API (pipes) │ │
│ └──────┬──────┘ └────────┬────────┘ │
│ └──────────┬───────┘ │
│ ┌─────▼─────┐ │
│ │ Commands │ │
│ └─────┬─────┘ │
│ ┌──────────┴──────────┐ │
│ ┌─────▼─────┐ ┌──────▼──────┐ │
│ │ DistFS │ │ World │ │
│ │(blockchain)│ │(peer mgmt) │ │
│ └─────┬─────┘ └──────┬──────┘ │
│ └──────────┬──────────┘ │
│ ┌─────▼─────┐ │
│ │ Payload │ │
│ └─────┬─────┘ │
│ ┌─────▼─────┐ │
│ │ Packet │ │
│ └─────┬─────┘ │
│ ┌─────▼─────┐ │
│ │ Network │ │
│ │ (UDP) │ │
│ └───────────┘ │
└─────────────────────────────────────────┘
- CMake >= 2.8.9
- OpenSSL (libssl-dev)
- libev (libev-dev)
- json-c (libjson-c-dev)
- readline (libreadline-dev)
- CUnit (libcunit1-dev) - for tests
sudo apt-get install build-essential cmake libssl-dev libev-dev \
libjson-c-dev libreadline-dev libcunit1-dev
git clone https://github.com/farb3yonddriv3n/skypeernet.git
cd skypeernet
mkdir build && cd build
cmake ..
make
sudo make installdocker build -t skypeernet .
docker run -it skypeernet# Create configuration directory
mkdir -p /etc/skypeernet
# Copy and edit configuration
cp config/settings.cfg /etc/skypeernet/tracker.cfg
# Start tracker
spntracker# Edit peer configuration (set tracker_ip to your tracker's IP)
cp config/settings.cfg /etc/skypeernet/peer.cfg
# Edit tracker_ip in peer.cfg
# Start peer
spnpeer# Inside spnpeer CLI:
# 1. Put file in finalized directory
# (copy your file to ~/finalized/myfile.txt)
# 2. Create a transaction to share it
ta myfile.txt "my tags here"
# 3. Mine the block to commit the transaction
bm
# 4. After mining completes, advertise your block to peers
ba a# Inside spnpeer CLI:
# 1. List remote files available
lf r
# 2. Find the file hash you want and add download job
ja <64-char-file-hash>
# 3. Check download progress
jd
# 4. When complete, finalize the file
jf <64-char-file-hash>
# File will be in ~/finalized/Configuration file: /etc/skypeernet/peer.cfg (or tracker.cfg)
{
"tracker_ip" : "127.0.0.1", // Tracker IP address
"tracker_port" : 5775, // Tracker UDP port
"proxy" : 0, // Act as proxy (0=no, 1=yes)
"download_directory" : "downloads", // Temp download chunks
"block_directory" : "block", // Blockchain storage
"keys_directory" : "keys", // RSA keys
"finalized_directory" : "finalized", // Completed files
"tcp_directory" : "tcp", // TCP tunnel temp files
"interval_retry" : 0.050, // Packet retry interval (sec)
"interval_peers_reachable" : 5, // Peer check interval (sec)
"mining_target" : 2, // Mining difficulty (leading zeros)
"send_factor" : 2, // Send rate multiplier
"max_task_buffer" : 51200, // Max task buffer size
"max_send_queue" : 50, // Max packets in send queue
"max_send_retry" : 200, // Max retry attempts
"max_peer_unreachable" : 5, // Max unreachable count before removal
"max_chunk_size" : 524288000, // Max chunk size (500MB)
"max_upload_kbytes" : 2048, // Upload limit (KB/s)
"max_download_kbytes" : 4096, // Download limit (KB/s)
"pipe_read" : "/tmp/skypeernet_read", // API read pipe
"pipe_write" : "/tmp/skypeernet_write", // API write pipe
"tcp_allowed_ports" : [22, 80, 443], // Exposed TCP ports
"tcp_ports_description" : "SSH, HTTP" // Description for peers
}| Command | Alias | Arguments | Description |
|---|---|---|---|
peers |
p, l, list |
- | List all connected peers |
whoami |
w |
- | Show your IP, port, and public key hash |
msg |
m |
<host> <port> <message> |
Send message to peer |
filesend |
fs |
<host> <port> <filename> |
Send file to peer |
traffic |
tf |
- | Show upload/download statistics |
| Command | Alias | Arguments | Description |
|---|---|---|---|
whitelist |
wl |
- | List whitelisted public key hashes |
whitelist_add |
wa |
<pubkeyhash> |
Add peer to whitelist |
whitelist_rem |
wr |
<pubkeyhash> |
Remove peer from whitelist |
| Command | Alias | Arguments | Description |
|---|---|---|---|
tadd |
ta |
<filename> <tags> [plain | to <pubkeyhash>,...] |
Create transaction to share a file; plain = unencrypted, to = grant listed peers access |
tshare |
ts |
<filehash> |
Re-share existing file from network |
tlist |
tl |
- | List pending transactions |
bmine |
bm |
- | Mine a block with pending transactions |
blocksaction |
ba |
u or a |
Update (u) or Advertise (a) block |
| Command | Alias | Arguments | Description |
|---|---|---|---|
listfiles |
lf |
l or r |
List local (l) or remote (r) files |
jobadd |
ja |
<filehash> |
Start downloading a file |
jobdump |
jd |
- | Show download job status |
jobfinalize |
jf |
<filehash> |
Finalize completed download |
jobremove |
jr |
<filehash> |
Remove/cancel a download job |
jobclean |
jc |
- | Clean up completed jobs |
| Command | Alias | Arguments | Description |
|---|---|---|---|
taskdump |
ad |
- | Show active file transfer tasks |
taskcancel |
ac |
<taskindex> |
Cancel a transfer task |
keysdump |
kd |
- | Show RSA key information |
keysexport |
kx |
- | Export own public key to keys/<pubkeyhash>.pub.pem for sharing |
keysend |
ks |
<filehash|gkhash> <pubkeyhash> |
Send a file key or group key to an online peer (grant access without re-mining) |
keygroup |
kg |
- | Create a group key; share files to it with ta ... to <gkhash> |
rdump |
rd |
- | Dump rogue peer info |
versiondump |
v |
- | Show version information |
| Command | Alias | Arguments | Description |
|---|---|---|---|
tunneladd |
ua |
<pubkeyhash> <port> |
Open tunnel to peer's TCP port |
tunneldump |
ud |
- | List active tunnels |
endpointdump |
ed |
- | List tunnel endpoints |
See docs/encryption.md for the full design.
By default, every shared file is encrypted with AES-256-GCM under a random
per-file content key (CEK). The CEK is stored in the transaction as a "key
slot", wrapped with the recipient's RSA public key. Your identity RSA keypair
(.priv.key / .pub.key) is used for authentication and unwrapping key
slots only — it never leaves your machine.
┌─────────────────────────────────────────────────────────────────┐
│ FILE ENCRYPTION (when sharing) │
│ │
│ CEK = 32 random bytes (fresh per file) │
│ │ │
│ Original File ──────────────┼──► Encrypted Chunks │
│ Filename ───────────────────┼──► Encrypted Description │
│ │ │
│ Transaction key slots: │
│ { to: your_pubkeyhash, key: RSA(your_pubkey, CEK) } │
│ │
│ Each chunk gets unique IV (timeiter) │
└─────────────────────────────────────────────────────────────────┘
To finalize (decrypt) a downloaded file, your peer looks for a key slot addressed to your public key hash and unwraps the CEK with your own private key. No slot for you = no access; the sharer must grant it.
Encryption schemes ("scheme" in lf r output):
| Scheme | Meaning |
|---|---|
0 plain |
not encrypted, anyone can download and finalize |
1 legacy |
pre-CEK blocks: AES key derived from the sharer's private key |
2 cek |
per-file content key + key slots (current default) |
Append plain to the tadd command to share a file unencrypted:
> ta myfile.txt "my tags" plainPlain files can be downloaded and finalized by any peer — no key exchange
is needed, and the filename is visible to everyone in lf r
("encrypted": false). Chunks are stored and transferred as-is, so use this
only for content you consider public.
Add key slots for other peers at share time:
> ta myfile.txt "my tags" to <pubkeyhash>[,<pubkeyhash>...]The recipient's public key is resolved automatically, in this order:
keys/<pubkeyhash>.pub.pem— an imported public key file- the live peer list — public keys are exchanged during peer discovery, so any currently online peer can be granted access directly
To let someone grant you access while you are offline, export your public
key with kx and hand them the file; they place it in their keys directory
and restart. Sharing a public key is safe — it cannot impersonate you or
decrypt anything.
> kx
Public key exported to keys/<your_pubkeyhash>.pub.pemTo grant access to an already-shared file (no re-mining needed), send the key directly to an online peer:
> ks <filehash> <pubkeyhash>The CEK is wrapped with the recipient's public key and sent over the
network (COMMAND_KEY). The recipient verifies it unwraps with their own
private key, stores it as keys/<filehash>.cek, and can finalize the file
from then on. A key that doesn't unwrap (not addressed to you) is ignored,
and an existing stored key is never overwritten.
A group key is a symmetric AES-256 key identified by its SHA256 hash. It
lives in the same hash namespace as peer keys, so ta ... to and ks
accept it transparently:
> kg # create a group key
Group key created: <gkhash>
> ta report.pdf "weekly" to <gkhash> # share a file to the group
> ks <gkhash> <member_pubkeyhash> # add an online member (they load it live)Members hold keys/<gkhash>.grp (base64 of the key; also importable
offline by copying the file and restarting) and can decrypt every file
shared to that group — including files shared before they joined — and can
pass the group key on. Treat the group key as shared trust: anyone holding
it is a full member. To evict members, create a new group key with kg
and share future files to the new hash.
Blocks created before the CEK scheme use the legacy scheme: the AES key is
SHA256(sharer's private key). To finalize such files you still need the
sharer's private key imported into keys_directory:
~/keys/
└── <pubkeyhash>.pem # legacy shared private keys (64-char hex names)- Get the sharer's private key file (out-of-band)
- Rename it to their
pubkeyhashwith.pemextension and put it in~/keys/ - Restart spnpeer (keys are loaded at startup); check with
kd
| Scenario | Key Required | How to Get It |
|---|---|---|
Download a plain-shared file |
None | - |
| Download your own files | Your private key | Already have it (auto) |
| Download a peer's CEK file | A key slot addressed to you | Sharer uses ta ... to <yourhash> or ks |
| Download a group-shared file | The group key | Any member sends it (ks <gkhash> ...) or hands you the .grp file |
| Download a legacy file | Sharer's private key | They share it (deprecated) |
┌──────────────────────────────────────────────────────────────────┐
│ YOUR MACHINE │
│ │
│ 1. Place file in ~/finalized/ │
│ $ cp myfile.pdf ~/finalized/ │
│ │
│ 2. Create transaction (in spnpeer CLI) │
│ > ta myfile.pdf "pdf documents work" │
│ Transaction added │
│ │
│ 3. Mine the block │
│ > bm │
│ Mining finished. It took 2.345 seconds │
│ │
│ 4. Advertise to network │
│ > ba a │
│ Block action 2 to 5 peers │
│ │
│ ✓ File is now visible to other peers via "lf r" │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ YOUR MACHINE │
│ │
│ 1. List available remote files │
│ > lf r │
│ { │
│ "files": [{ │
│ "name": "myfile.pdf", │
│ "hash": "a1b2c3d4...64chars...", │
│ "pubkeyhash": "f6e5d4c3...64chars...", ◄── NOTE THIS! │
│ "size": 1048576, │
│ "downloaded": false │
│ }] │
│ } │
│ │
│ ⚠️ BEFORE DOWNLOADING: Ensure you have the sharer's private │
│ key in ~/keys/<pubkeyhash>.pem (see "Encryption and Keys") │
│ │
│ 2. Start download job (use the 64-char hash) │
│ > ja a1b2c3d4e5f6... │
│ Job a1b2c3d4e5f6... added │
│ │
│ 3. Monitor progress │
│ > jd │
│ { │
│ "jobs": [{ │
│ "file": "a1b2c3d4...", │
│ "progress": "75%", │
│ "chunks_done": 3, │
│ "chunks_total": 4 │
│ }] │
│ } │
│ │
│ 4. Finalize when complete (100%) │
│ > jf a1b2c3d4e5f6... │
│ │
│ ⚠️ If finalization fails silently, you're missing the key! │
│ │
│ ✓ File is now in ~/finalized/myfile.pdf │
└──────────────────────────────────────────────────────────────────┘
Files in skypeernet are identified by SHA256 hashes (64 hexadecimal characters):
File: myfile.pdf
Hash: a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef
This hash is used to:
- Uniquely identify the file across the network
- Verify file integrity after download
- Reference files in commands (ja, jf, jr, ts)
TCP tunneling allows you to access TCP services on remote peers through the P2P network.
# On your machine (inside spnpeer CLI):
# 1. Get the peer's public key hash
> p
# Find the peer and note their pubkeyhash
# 2. Open tunnel to their SSH port (22)
> ua a1b2c3d4e5f6...pubkeyhash...64chars 22
{
"success": true,
"src_port": 12345, # Your local port
"dst_port": 22 # Remote SSH port
}
# 3. Now connect via the tunnel
$ ssh user@localhost -p 12345
# This connects to the remote peer's SSH through the P2P network!
# 4. Check tunnel status
> ud
# 5. Close tunnel when done (not shown - tunnels persist until peer restart)Web-based management interface accessible at http://localhost:8080
cd plugins/johnconnor
./rebar get-deps
./rebar compile
./rel/files/erws startAutomated file synchronization daemon.
cd plugins/skynet
pip install -r requirements.txt
python skynet.py- Check tracker IP in configuration
- Ensure UDP port 5775 is open
- Verify tracker is running:
netstat -ulnp | grep 5775
- Check peer connectivity:
p(peers command) - Verify file exists remotely:
lf r - Check job status:
jd - Look at tasks:
ad
Most common cause: Missing decryption key!
- Check the file's
pubkeyhashwithlf r - Verify you have the key:
kd(should show matching shared key) - If missing, obtain the private key from the file sharer
- Place key in
~/keys/<pubkeyhash>.pem - Restart spnpeer (keys only load at startup)
- Try
jf <hash>again
Reduce mining_target in configuration (fewer leading zeros = easier mining)
- Set
proxy: 1on public-facing peers - Use TCP tunneling for direct connections
- Check if peer shows
shadow: true(being routed through proxy)
Check syslog for detailed logs:
tail -f /var/log/syslog | grep -E "(distfs|spnpeer)"See the header files in src/include/ for detailed protocol documentation:
packet.h- Packet structure and fragmentationpayload.h- Command handlersdata.h- Serialization formatpeer.h- Peer state machinenet.h- Network layerworld.h- Peer discovery
GPL 3.0 - See LICENSE file.
Pull requests welcome! Please ensure:
- Code follows existing style
- Tests pass:
./spntest - No compiler warnings with
-Wall

