Peernet Whitepaper
A decentralized internet.
Version 0.10 — 2026-11-02
Abstract: Peernet is a new peer-to-peer network in which individuals are in full control of their data. Peernet is neutral and data agnostic. The network is fully controlled and owned by its users. Data is shared freely.
Introduction
Peernet is a public peer-to-peer network. Each user holds their private key which gives them full control over their data. This private key manages the user's personal blockchain which stores metadata of shared files and folders, as well as the user's +1s and follows. Users may publish, share, and remove their data at their discretion. Content is not tied to a single host: peers that follow a peer may mirror its blockchain and files, so the data stays available while any of them is online.
The network hierarchy is organized in a Kademlia-like fashion. The Peer ID is the user's public key (Ed25519 is used); the Node ID is the blake3 hash of the Peer ID. The distance between two peers is measured as XOR of their Node IDs. Two Distributed Hash Tables (DHTs) are used: DHT A to discover peers, DHT B to discover who stores data. There is no central authority; each peer makes its own decisions on the extent to rely on and trust other peers.
At any time, the user is in control of what data he publishes and what data he consumes.
Terminology
| Term | Definition |
|---|---|
| Client | A client is a software program that participates in the Peernet. |
| Decentralized Internet | In this phrase, "internet" refers to application-level services recognized by a typical user. Peernet connects over the internet as well as LANs. |
| Root Peer | A peer operated by a known trusted entity. They allow to speed up the network including discovery of peers and data. The chain of trust is established by hard coding such peers into the client. Users may choose to remove those peers from the trust list and add their own curated list. |
| User | Human. |
| Peer | An endpoint participating in the peer-to-peer network. Usually a Computer. |
| Peer ID | The peer's Ed25519 public key. It identifies the peer and is the root of its blockchain. |
| Node ID | The blake3 hash of the Peer ID. It is the peer's position in DHT A. |
| DHT A | The peer discovery DHT. Keyed by Node ID; resolves a Peer ID to the peer's addresses. It serves no other purpose. |
| DHT B | The data storage DHT. Keyed by the blake3 hash of a data identifier (the Peer ID for a blockchain, the file hash for a file); resolves it to the peers that store a copy. |
| +1 | A signed reference in the user's blockchain to a specific piece of content (publisher key and File ID) marking it as valuable. |
| Follow | A signed reference in the user's blockchain to another peer, marking that peer's blockchain as one the user tracks. |
| Bootstrapping | The process of discovering the initial set of peers to connect to the network. |
| Message | A packet sent from one peer to another. |
| Offset and Size | In bytes unless otherwise specified. |
Trust
Trust is decided by each peer. The protocol defines the trust signals, not their evaluation.
Root peers are trusted by default; the user may remove them from the trust list and add his own. Follow and +1 are signed records in the user's blockchain: a peer the user follows, or whose data the user has given a +1, is trusted by that user. A peer may record a blacklist entry in its blockchain marking another peer as spam. Since all blockchains are public via DHT B, any peer may read other peers' follows, +1s and blacklist entries and use them as input for its own trust decisions.
Neutrality
Peernet is neutral and data agnostic. The protocol transports and stores whatever data its users publish, in the same way that TCP/IP or HTTP carry any payload; the protocol does not inspect, classify, rank, promote or restrict data, and has no knowledge of its meaning.
Each user holds the private key that allows the user to extend his or her own blockchain – including deleting data by publishing a new version of the blockchain without it. The private key is the identity: there is no recovery mechanism, and a lost key means a lost identity and blockchain. Data is published by the user, under the user's key and by the user's decision; no other party can publish, alter or remove it. Only the private key holder can remove data from his or her blockchain; a request to remove data, including a legal demand, can therefore be fulfilled by the key holder alone.
Which data a peer stores, mirrors, or displays is decided by that peer. Users may use content blacklists, peer blacklists and trust lists; their use is the user's decision.
Protocol
The functionality of the protocol is separated into:
- Peer discovery
- Bootstrapping
- DHT A for peer discovery using blake3 hashes for keys.
- Metadata sharing
- DHT B to find who stores a blockchain (keyed by its Peer ID).
- DHT B to find who stores a file (keyed by its file hash).
- File transfer
Peernet only uses UDP. The default port for exchanging packets is 'p' (112). UDT (UDP-based Data Transfer Protocol) is used for file transfer. If the default port is unavailable, a random port may be used. (The only reason for the default port is for initial discovery in isolated networks.)
Peers are identified via their Ed25519 public key (= Peer ID). At the same time, it is the root of the peers' blockchain. This allows anyone to verify the blockchain of that peer and the signatures of its messages. Blockchains are versioned, and if a newer version appears, the old one must be deleted. This allows to restructure a blockchain if it becomes too big and contains dead entries like deleted file descriptors.
The client software is identified via a User Agent which must include the software name and version number. Peers may have multiple IP addresses assigned at the same time. Public and private IPs are supported (although private IPs may not be shared publicly) and IPv4/IPv6 dual stack is supported.
Routing of peers follows the Kademlia protocol. Neighbors are discovered using the XOR distance between Node IDs. The Node ID is the blake3 hash of the public key. Blake3 is used for hashing the keys of DHT A and DHT B. Any peer may store a copy of any blockchain and announces it in DHT B, as with any other file.
Each peer may store any file. It will announce that it stores the file in DHT B. Peers maintaining DHT B may randomly verify that the listed peers are alive and store the files.
Packet Structure
Each UDP command packet has the following structure (see table below). The packet is Salsa20 encrypted using the receiver's Peer ID as key and signed using the sender's private key. The sender's public key is carried inside the encrypted part of the packet; the receiver decrypts with its own Peer ID, reads the sender's public key and verifies the signature against it. The purpose of the encryption is deliberately limited: it is traffic obfuscation and receiver binding, not confidentiality. Peernet is a public network and Peer IDs are public by design. Consequently, anyone holding the receiver's Peer ID can decrypt packets addressed to it. The scheme provides three properties:
- Traffic carries no plaintext structure and appears as random data. This defeats content-based traffic classification by ISPs and corporate firewalls, which are known to block or degrade peer-to-peer traffic.
- A packet is bound to one receiver. It cannot be redirected to or replayed against a different peer, since that peer will fail to decrypt it.
- Together with the signature, it protects against impersonation and tampering: the signature authenticates the sender, and any modification invalidates it.
Packets contain a random amount of garbage appended to the payload to prevent fingerprinting based on packet size.
| Offset | Size | Info | Encrypted (Salsa20, key = receiver ID) | Signed (Ed25519) |
|---|---|---|---|---|
| 0 | 4 | Nonce | ✓ | |
| 4 | 1 | Protocol version = 0 | ✓ | ✓ |
| 5 | 1 | Command | ✓ | ✓ |
| 6 | 4 | Sequence | ✓ | ✓ |
| 10 | 32 | Sender's public key (= sender's Peer ID) | ✓ | ✓ |
| 42 | 2 | Size of payload data | ✓ | ✓ |
| 44 | ? | Payload | ✓ | ✓ |
| ? | Randomized garbage. Protects against fingerprinting of the traffic based on size of packet. | ✓ | ✓ | |
| 64 | Signature Ed25519 by the sender over all preceding bytes. | ✓ |
The nonce must be pseudorandom generated (may be generated from a message counter), but it must not be an incremented number for each packet, as it might otherwise provide a vector for fingerprinting. The 4-byte nonce is calculated into an 8-byte nonce for Salsa20 by using the same 4 bytes twice. Messages that are responses to a request must use the same sequence number. It allows to filter out unsolicited responses and protect against replay and poisoning attacks. Messages like Response that potentially return multiple replies have a flag SEQUENCE_LAST that indicates the last reply in the sequence. Single reply messages like Pong do not need such a flag.
The scheme does not protect against an observer who holds the receiver's Peer ID and has built a Peernet protocol decoder. It is designed to defeat current real-world traffic classification, not a targeted adversary.
The scheme is versioned. Since the protocol version field is inside the encrypted part of the packet, a receiver cannot determine the version before decoding. Receivers therefore decode by trial: a packet is first decoded with the scheme defined here; if the signature does not verify, the receiver tries any newer scheme it supports, in order. Every scheme must include an authentication check (such as the signature) so that a wrong decoding is reliably rejected.
The target packet size shall, where possible, not exceed 508 bytes to prevent fragmentation and increase delivery probability. The next limit that should not be exceeded is 1472 bytes, which is right below the Ethernet MTU (maximum transmission unit) limit.2 The hard cap is 65507 bytes.3
Linux does by default a MTU discovery and uses it as upper limit for UDP packets. It returns EMSGSIZE if a UDP packet write exceeds it.4 This could be also a limitation on Android.5
The following commands exist. Responses may only be sent after receiving a corresponding request.
| # | Name | Info |
|---|---|---|
| 0 | Announcement | Message to a new peer to inform about self and to request information. Carries DHT A requests (find peers) and DHT B requests (find or announce stored data). |
| 1 | Response | Response to the announcement. Carries DHT A and DHT B results. |
| 2 | Ping | Keep-alive message (no payload). |
| 3 | Pong | Response to ping (no payload). |
| 4 | Local Discovery | Local discovery. |
| 5 | Traverse | Help establish a connection between 2 remote peers. |
| 6 | Get Block | Request blocks for specified blockchain. |
| 8 | Transfer | Start file transfer. |
| 16 | Search | Search for files. Response contains blocks or reference to blocks. |
Announcement
The announcement (command 0) is used for multiple purposes: initial connectivity (bootstrapping), (unsolicited) announcement of self, sending of neighbor peers, requesting closest peers for a Peer ID or file hash, and informing about data stored. It is up to the receiver whether to add the sender into its peer table, and whether to respond to some or all the requested actions.
| Offset | Size | Info |
|---|---|---|
| 0 | 4 bits | Protocol version support. Might be a higher number than used for sending this packet. |
| 0.5 | 4 bits | For future use. |
| 1 | 1 | Feature bit array. Informs about what the sender supports. See below. |
| 2 | 1 | Action bit array. The receiver is asked to provide the following. See below. |
| 3 | 8 | Sender's blockchain height. |
| 11 | 8 | Sender's blockchain version. Any stored previous versions must be deleted, and the blocks must be requested again. |
| 19 | 2 | Sender's internal listening port |
| 21 | 2 | Sender's external listening port, if known |
| 23 | 1 | Size of User Agent in bytes. Max 255. |
| 24 | ? | User Agent in the format "Software/Version number". Required in the initial announcement/bootstrap. Example: "Test Client/1.0". UTF-8 encoded. Must not contain null bytes. Max length is 255 bytes. |
| ? | ? | Additional data depending on the actions requested in consecutive order. |
Feature bit array:
| Bit | Feature | Info |
|---|---|---|
| 0 | IPv4_LISTEN | Sender listens on IPv4 |
| 1 | IPv6_LISTEN | Sender listens on IPv6 |
| 2 | FIREWALL | Sender indicates a potential local firewall |
Action bit array:
| Bit | Action | Info |
|---|---|---|
| 0 | FIND_SELF | Request closest neighbors to self |
| 1 | FIND_PEER | Request closest neighbors to peer |
| 2 | FIND_VALUE | Request data or closest peers |
| 3 | INFO_STORE | Sender indicates storing provided data |
Actions FIND_PEER and FIND_VALUE send an array of hashes to look up in the hash table:
| Offset | Size | Info |
|---|---|---|
| 0 | 2 | Count of keys that follow |
| 2 + 32 * n | 32 | Key = Node ID or blake3 hash of data |
Action INFO_STORE allows to inform other peers (usually the closest ones to the blake3 hash) that the sender stores certain data by sending the below structure. Receivers may choose to maintain that information in their hash table to inform others when requested (via FIND_VALUE). The protocol intentionally does NOT provide a push mechanism for data stored by 3rd party peers to prevent flooding and spam. The receiver may verify if the sender stores the data by requesting it. This can be done randomly to prevent unnecessary overhead. INFO_STORE does not trigger a response.
| Offset | Size | Info |
|---|---|---|
| 0 | 2 | Count of records that follow |
| Each record: | ||
| 0 | 32 | Key = blake3 hash of the data |
| 32 | 8 | Size of the data |
| 40 | 1 | Type of the file: 0 = File, 1 = Header file containing list of parts for splitting up large files and allowing transfer in pieces. |
Response
The response has the same basic format as the announcement, except that the actions field has a different meaning. Bit 0 of Action is the SEQUENCE_LAST flag. Multiple response messages may be sent for a single announcement. This structure follows the User Agent:
| Offset | Size | Info |
|---|---|---|
| ? + 0 | 2 | Count of peer responses |
| ? + 2 | 2 | Count of embedded files |
| ? + 4 | 2 | Count of hashes not found |
FIND_SELF, FIND_PEER, and FIND_VALUE use each below structure to report back peers. The announcement message uses the feature field (IPv4_LISTEN and IPv6_LISTEN) to indicate whether the response shall contain IPv4 and IPv6 addresses.
| Offset | Size | Info |
|---|---|---|
| 0 | 32 | Requested Key = blake3 hash. |
| 32 | 2 | Count of peer records that follow (15 bits). Bit 15 indicates the last result. |
| Each peer record: | ||
| 0 | 32 | Peer ID |
| 32 | 4 | IPv4 Address |
| 36 | 2 | IPv4 Port |
| 38 | 2 | IPv4 Internal port as reported by that peer |
| 40 | 2 | IPv4 External port as reported by that peer |
| 42 | 16 | IPv6 Address |
| 58 | 2 | IPv6 Port (actual one used for connection) |
| 60 | 2 | IPv6 Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT. |
| 62 | 2 | IPv6 External port as reported by that peer. This is used in case of port forwarding (manual or automated). 0 if not known. |
| 64 | 4 | Last contact with peer inbound in seconds |
| 68 | 1 | Feature bit array. Same format as in Announcement message. Bit 7 Reason: 0 = Peer is close to requested hash, 1 = Peer is known to store the data for requested hash |
A response to FIND_VALUE may provide the requested data as embedded file. This is only possible if the entire file fits into the UDP packet, otherwise it must be transferred using a file transfer request. Each embedded file has the following header:
| Offset | Size | Info |
|---|---|---|
| 0 | 32 | Key = blake3 hash |
| 32 | 2 | Size of data |
| 34 | ? | Data |
FIND_SELF, FIND_PEER and FIND_VALUE requests may result into not-found records. The hashes that were not found are stored at the end of the packet (as array of 32-byte hashes).
Local Discovery
The local discovery message is used to discover peers over the local network. It may only be sent as part of a local IPv4 broadcast, IPv6 multicast or brute-force. It may not be sent via the internet. Since the broadcast/multicast targets unknown recipients, it cannot use the receiver's Peer ID for encryption. Instead, it uses hardcoded private keys for encryption and decryption. Note that the local discovery feature with hard-coded keys is a tradeoff with deep packet inspection security. The structure is the same as the announcement message (the Action field is not used). The local discovery message has no response.
Traverse
The traverse message helps 2 remote peers to establish a connection to each other. If Peer M sends Peer A the information of Peer B, and A identifies that B is behind a NAT, it will send M the traverse message which forwards it to B. When B receives the message, it will verify the signature of A and then processes the embedded message as if it were sent from A directly.
In this case M acts as a proxy. If A sends both the Announcement message directly to B and via the Traverse message through M, B is likely to receive it. When B sends back the Response message it should automatically open the port on B's NAT for further communication.
Note that this UDP hole punching mechanism is likely to fail if both peers are behind symmetric NATs.
| Offset | Size | Info |
|---|---|---|
| 0 | 32 | End receiver Peer ID |
| 32 | 32 | Peer ID that is authorized to relay this message. |
| 64 | 8 | Expiration time when this forwarded message becomes invalid. |
| 72 | 2 | Size of embedded packet. |
| 74 | ? | Embedded packet. |
| 74 + ? | 64 | Signature by original sender. Must match the embedded packet. |
| ? | 4 | IPv4 address of the original sender. Set by authorized relay. |
| ? | 2 | IPv4 port used for connection. |
| ? | 2 | IPv4 external port as reported by the original sender. |
| ? | 16 | IPv6 address of the original sender. Set by authorized relay. |
| ? | 2 | IPv6 port used for connection. |
| ? | 2 | IPv6 external port as reported by the original sender. |
Transfer
The transfer message initiates and continues a file transfer. For the purpose of clarity, the peer requesting a file is considered the client, and the peer serving the file the server. The client must know before the transfer that the server stores the file.
| Offset | Size | Info |
|---|---|---|
| 0 | 1 | Control (see below) |
| 1 | 1 | Transfer Protocol: 0 = UDT |
| 2 | 32 | File Hash |
| 34 | ? | Embedded protocol data |
Control values:
| Control | Info |
|---|---|
| 0 | Request start transfer (client → server). Data at byte 34 is offset and limit to read, each 8 bytes. |
| 1 | Not available (server → client) |
| 2 | Active transfer (server → client) |
| 3 | Terminate (both way) |
Blockchain Exchange
The get block message requests specified blocks for a blockchain from a remote peer. The blockchain might be the remote peer's own blockchain, or any other that the remote peer may cache. The actual transfer of the blocks is done via UDT.
Design Considerations
The design considerations for this protocol were:
- First packet must establish connection. This includes sending metadata and actions.
- Traffic appears as random data to third parties that do not hold the receiver's Peer ID (obfuscation against traffic classification, not confidentiality).
- Secure against message forgery.
- Secure against tampering and by extension bitflip.
- Small as possible to increase deliverability.
- TCP-like acknowledgment and resending are not desired due to the decentralized nature of the network and natural concurrency. The exception to this is file transfer.
Blockchain
The blockchain height and version number are 64-bits, which is the absolute limitation on the user's modifications of his blockchain. Adding files, removing them, renaming them, changing other metadata tags etc. produces additional blocks (and increasing the height). However, when there are many deletions or many small blocks, it makes sense to instead increase the version number and start the height at 0. This is beneficial to keep the blockchain small when many files are deleted, while at the same time increasing privacy. There is no hard cap of block sizes, but clients may choose not to download large blocks. The absolute minimum safe block size is 1 KB (clients must not use max block size limits smaller than that). The recommended (minimum) target block size to accept is 4 KB.
Block header:
| Offset | Size | Info |
|---|---|---|
| 0 | 64 | Signature of entire block by the blockchain owner |
| 64 | 32 | Hash (blake3) of last block. 0 for first one. |
| 96 | 8 | Blockchain version number |
| 104 | 8 | Block number |
| 112 | 4 | Size of entire block including this header |
| 116 | 2 | Count of records that follow |
Each block record that follows the block header has the below format. The record type identifies the structure of the data field and allows new record types to be defined in the future. To completely remove a record, the blockchain is rebuilt without that record and the version number increased; this is the only deletion mechanism, there is no delete record. A rebuilt blockchain must preserve the original order of the remaining records. All record types and the encoding of the data are defined in the reference implementation. Example records include profile data, files, and social interactions.
| Offset | Size | Info |
|---|---|---|
| 0 | 1 | Record type. |
| 1 | 8 | Date created. This remains the same in case of block refactoring. |
| 9 | 4 | Size of data |
| 13 | ? | Data. Encoding depends on the record type. |
The encoding of profile data (record type 0) is 2 bytes the profile type followed by the profile data.
This is the encoding of files (record type 2):
| Offset | Size | Info |
|---|---|---|
| 0 | 32 | Hash blake3 of the file content |
| 32 | 16 | File ID (UUID version 7) |
| 48 | 32 | Merkle Root Hash |
| 80 | 8 | Fragment Size |
| 88 | 1 | File Type |
| 89 | 2 | File Format |
| 91 | 8 | File Size |
| 99 | 2 | Count of Tags |
| 101 | ? | Tags |
The File ID is a UUID version 7 (time-ordered), generated when the file is published. It identifies the publication, whereas the content hash identifies the content: the same content may be published more than once, each time with a new File ID. Because UUID version 7 embeds the creation time and blocks are appended in order of creation, File IDs are monotonic across a blockchain, which allows a very large blockchain to be searched for a File ID by binary search rather than a full scan.
Referencing Data in Other Blockchains
A file is referenced by the publisher's public key and the File ID. References never address a block position, since the publisher may rebuild the blockchain at any time. A reference may additionally carry the blockchain version and block number as a non-authoritative locator hint: a peer resolving the reference first fetches that single block and checks whether it contains the File ID; if not (for example after a rebuild), it falls back to the publisher's blockchain or its index. The hint is cheap and makes resolution effective at scale. A reference to data that the publisher has since deleted does not resolve, which is the intended behavior.
Peer Discovery
Initial peer discovery is done via bootstrapping: a hard-coded list of root peers for the internet, and the Local Discovery message (IPv4 broadcast, IPv6 multicast) for the LAN. Peernet does not use Multicast DNS due to its unreliability.
Ongoing peer discovery is accomplished by exchanging peer lists via the Announcement and Response messages (DHT A). Clients shall prioritize exchanging high quality peers. High quality can be determined by uptime, reach (for example, is the port forwarded), and other metrics such as connection speed and ping time.
File Transfer
Transfer of file data uses a forked (slightly trimmed down) version of UDT embedded in the Transfer message. It handles packet loss, congestion, and rearranging the packets into a continuous stream.
TODO
File Discovery
Peers that store a file announce it in DHT B, keyed by the file hash. Peers may index files and associated metadata tags in a sanitized way and make it searchable.
TODO
URI
Outside Peernet, data is referenced by a URI. It names the publisher by Peer ID, followed by a format indicator that defines the structure of the remainder:
peer://[PEER ID]
peer://[PEER ID]/0/[FILE ID]
peer://[PEER ID]/1/folder/filename
[PEER ID] is the Peer ID hex encoded, 64 characters, case insensitive. A URI consisting of the Peer ID alone references the peer itself, that is its blockchain.
| Format | Remainder |
|---|---|
| 0 | The File ID in UUID text form, 36 characters, case insensitive. |
| 1 | Folder and file name, the file record's folder and name tags. |
Notes
- https://stackoverflow.com/questions/14993000/the-most-reliable-and-efficient-udp-packet-size ↩
- https://stackoverflow.com/questions/1098897/what-is-the-largest-safe-udp-packet-size-on-the-internet ↩
- https://manpages.ubuntu.com/manpages/bionic/man7/udp.7.html ↩
- https://groups.google.com/g/android-ndk/c/UXvR_yCaH0Q ↩