Root Network

From OpenP2P

Revision as of 22:03, 14 February 2014 by Scross (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The OpenP2P Root Network is a network designed to facilitate the construction of other peer-to-peer networks, referred to here as 'sub-networks'. Primarily, the network is designed to be an efficient way to bootstrap to these other networks, and to locate specific nodes on the network.



The basic functions are:

  • Establish a pseudo-anonymous reliable identity (based on a public key).
  • Verify the pseudo-anonymous identities of other nodes.
  • Join the network, given the address of one or more participating nodes.
  • Establish an authenticated and encrypted communications channel.

Network nodes are identified through a pseudo-anonymous 256 bit ID, which is a SHA-256 hash of the node's public key (ECDSA). Messages are designed to be as small as possible so that the Root Network is an extremely light weight protocol. The network is extensible through the use of 'sub-networks' (the Root Network can also be used to bootstrap to these networks).

Sub-networks are identified by the first 8 bytes of the MD5 hash of their network name. For example, the DHT sub-network "p2p.rootdht" (described below) has the network ID 0xfaf7e5b6e98317bc.

Protocols and Endpoints

The Root Network is designed to work over multiple protocols/transport mechanisms, such as UDP, TCP, USB etc. For small individual messages where packet loss is accepted (e.g. DHT messages), UDP is preferred over TCP because packets are smaller (TCP requires space for its header) and there is no need to establish a connection (which takes three messages in TCP). However, TCP connections should be used where a reliable stream connection is required (e.g. FolderSync).

Messages use a standard format for endpoints, consisting of a 16 bit unsigned integer that gives the type of an endpoint, followed by the endpoint's data.

The endpoint type values in the 16 bit unsigned integer are:

  • 0 -> Local
  • 1 -> UDP over IPv4
  • 2 -> UDP over IPv6
  • 3 -> TCP over IPv4
  • 4 -> TCP over IPv6


In the Root Network, it is not expected or required that all nodes can connect directly to each other. In order that messages travel between nodes that cannot connect directly to each other, they are routed by intermediate nodes.

The system is simple: nodes attempt to make direct connections to endpoints where possible, otherwise they use the endpoint of the node that delivered the node's endpoint to them. Hence an endpoint in the Root Network is not necessarily the endpoint of the node itself, but may an intermediate node, which should forward any messages sent to it with the correct destination ID.

Note that all messages are signed with the sender's public key so intermediate nodes cannot 'steal' the identity of the node for which they are routing. However, it is important to consider that messages are not encrypted, which means data sent via Root Network messages can be observed by eavesdroppers; the KEY_EXCHANGE message can be used to establish an authenticated and encrypted communications channel.

An example of routing would be a computer A connected to another computer B over the internet, which itself is connected to a device C over USB: A can discover C (and vice versa) via the Root Network, since B routes messages between them. Any intermediate devices can choose to not route messages if appropriate, though nodes should favour throttling rather than blocking communications. Correspondingly, nodes relying on routing can select alternative routes to attempt to bypass such throttling.

Nodes should never reveal the endpoints of other nodes outside of purpose designed sub-networks. For example, nodes should not reveal the endpoint of a node without DHT support through the DHT sub-network.


All messages are a part of a routine.

Most routines have just two messages: a request and a reply. However, other routines can contain more messages.

A routine is only between two nodes, and all messages are from one node to the other node.

Message structure

Each message consists of three sections (represented in this order):

  • Header - gives information about the message.
  • Data - contents and size depends on request type.
  • Identity - contains a signature of the message by the sender's private key, and the sender's public key.

The header comes first since it includes the version number, which can affect the fundamental structure of the rest of the message.


byte offset 0–7 8-9 10 11 12–15 16-31
0 Version State ERR SUB Message Type Length
4 Routine Identifier
8 Message Counter
16 Destination Identifier
An 8 bit value giving the version number for which the message is structured. Currently the only version number is 1. If a node receives a message with a version it doesn't support, it should produce a reply with the version field set to the latest it supports and the initiating node should detect this and send a fresh request with this version - the protocol is designed to support full backwards compatibility.
A 2 bit value giving the state of the routine of which this message is a part. An initial request has a value of 0, a reply to that request has a value of 1 etc. Clearly, the limits of this value means that routines can have up to 4 messages.
A bit that indicates whether this message represents an error. This is typically used in replies for indicating problems with a request (such as an invalid signature).
A bit that indicates whether this message is a sub-network message; general root network messages use a value for zero for this field.
Message Type 
For general root network messages (SUB bit set to 0), a 4 bit value giving the type of the message:
  • 0 -> IDENTIFY
  • 1 -> PING
  • 2-3 -> Reserved for future use.
  • 5-7 -> Reserved for future use.
  • 9-15 -> Reserved for future use.
A 16 bit value giving the length of the entire message (i.e. including header and signature).
Routine Identifier 
A 32 bit value identifying the routine of which this message is a part. This value is created by the node which starts the routine; receivers simply copy this value into their replies.
Message Counter 
A 64 bit value used by a node to indicate the order of all messages they send, which can be verified by receivers (i.e. to prevent replays), and its value should start at 0.
Destination Identifier 
256 bit destination DHT identifier.

In accordance with networking standards, all multi-byte values are represented in big-endian order.



An IDENTIFY message is sent to an endpoint when nothing is known about its ID.

Requests: A zeroed destination ID (since it's not known) in the message header.

Replies: Data section contains a single endpoint, which is the endpoint of the sender from the perspective of the receiver.


A PING message is equivalent to an IDENTIFY message except that the message is sent to an ID rather than an endpoint, and so is subject to routing. This message is usually used to determine if a node can be reached.

Requests: Data section is empty.

Replies: Data section includes a single endpoint, which is the endpoint of the sender from the perspective of the receiver.

Note that the endpoint in the reply is after routing has occurred, so may be the actual endpoint of another node which routed the message. IDENTIFY messages should be used where it is important to determine the characteristics of NATs.


This message is sent to an ID to query the sub-networks supported by the associated node.

Requests: Data section is empty.

Replies: Data section includes the network ID (the first 8 bytes of the MD5 hash of the network name) of each sub-network supported by the node.


This message performs an Elliptic Curve Diffie Hellman (ECDH) exchange in order to establish a secure authenticated communications channel between two nodes.

Requests: Data section contains a 256 bit ECDH public key generated by the initiator.

Replies: Data section contains a 256 bit ECDH public key generated by the receiver.

Once the procedure has completed, both nodes will possess a secret which is unknown to observers. The authentication provided by Root Network messages (i.e. signing every message with the node's private key) also make man-in-the-middle attacks infeasible.

DHT Subnetwork

Network name: "p2p.rootdht"

One of the key sub-networks is the DHT discovery sub-network, based on a Kademlia DHT, which enables node search and sub-network discovery in O(log n) time (where n is the number of nodes in the DHT). Specifically, this network provides the following:

  • Locate a particular node on the network by its ID.
  • Subscribe to sub-networks (given by DHT IDs).
  • Get a list containing a selection of users on a particular sub-network.

Since this is a sub-network, messages for this network have the SUB bit set to 1.

The following message types apply for this network:

  • 1-7 -> Reserved for future use.
  • 8 -> SUBSCRIBE
  • 10-15 -> Reserved for future use.


Requests: Data section contains a 256 bit ID for the target node ID.

Replies: In the data section each node ID is given (256 bits), followed by its endpoint, up to the number of nodes specified in the header.


Requests: Data section contains a 256 bit ID of the sub-network to be subscribed to.

Replies: Data section is empty; this message is simply an acknowledgement.

When a node subscribes to a sub-network it asks the DHT to store its node data (its ID and address info). Nodes that get subscribers will then receive a list of nodes, each of which it should contact directly to obtain information about how to contact them for the sub-network.


This is identical to GET_NEAREST_NODES in structure, although in this case the purpose is clearly to obtain a list of subscribers, with the ID in the request being the sub-network ID.


The identity section contains (represented in this order):

The signature is produced using SHA-256, and should be 64 bytes (512 bits -> twice the size of the key) in length.

The public key uses the brainpoolp256r1 curve, so the public key is 256 bits in length. The value in this section is the encoding of the X coordinate of the public point, as described in Compact representation of an elliptic curve point, hence it is also 256 bits (32 bytes).


Rather than ignoring messages with errors such as invalid signatures or improper structure, nodes will send a reply with the ERR bit set in the header. However, this should only be performed by the destination node; intermediate nodes (in routing) should route messages even if they are invalid (since nodes will look for replies based on the sender).

When ERR is set, the message type field in the header becomes a 4 bit error code. The routine identifier must match that of the original message. Its value can be one of the following:

  • 4-7 -> Reserved for future use.
  • 9-16 -> Reserved for future use.

Replay attack

The message counter prevents replay attacks, which could introduce outdated information into the network, since it is easy to identify old messages. Furthermore, it provides a clear ordering to the messages.

Note that checking incoming message request counts is more complex than checking them against a counter, since messages could be delivered out of order on some protocols (e.g. UDP). A good solution is to have a counter value and a 'window' of boolean values indicating whether the counter value has appeared in a sent value (after which it cannot be used again). Each time a message arrives with a value greater than the counter, it is advanced to that value, and the window is shifted by that amount. Messages with a value lower than the counter are checked against the window (if thee values is not less than the counter value - the window size, otherwise they are simply rejected). This simply needs to be a fixed size bitset, and is unlikely to need to be longer than 8 entries, hence taking a byte of storage per node (in addition to the 8 bytes for the message counter value).

Sybil attack

As the Root Network uses a DHT, it is vulnerable to the Sybil attack, which in this case would be an attacker creating multiple different DHT entities and then using them to restrict access to some part of the network. Similarly, an attacker can push the node data of all of its entities to the DHT, hoping that other legitimate entities are unlikely to be selected or will be removed over time to save space.

The primary solution to this problem revolves around redundancy: it doesn't matter if a single node returns useless results, we will always ask many nodes so we are likely to get some legitimate results. The same applies to storing data: always store it with many different nodes so it has the best chance of being retrieved. The solution also takes advantages of IP addresses - some systems uses the IP address or a hash of it as the DHT ID, however in this case we just want to get as wide a spread of IP addresses as possible, assuming that most attackers won't have enough widely spread IP addresses as compared to legitimate users. To make this work, IP addresses must be verified (by sending a message to the IP address in expectation of a reply). Other solutions that are likely to be deployed are favoring nodes that provided accurate information in the past, and favoring older nodes.

It is important to note that nodes cannot join the network pretending to be another node, as the node's ID is derived (SHA-256) from its public key, and all messages it sends must be signed with the corresponding private key, hence it must prove it 'owns' the ID.

Personal tools