ZeroNet network protocol

  • Every message is encoded using MessagePack
  • Every request has 3 parameter:
    • cmd: The request command
    • req_id: The request's unique id (simple, incremented nonce), the client has to include this when reply to the command
    • params: Parameters for the request
  • Example request: {"cmd": "getFile", "req_id": 1, "params:" {"site": "1EU...", "inner_path": "content.json", "location": 0}}
  • Example response: {"cmd": "response", "to": 1, "body": "content.json content", "location": 1132, "size": 1132}
  • Example error response: {"cmd": "response", "to": 1, "error": "Unknown site"}

Handshake

Every connection begins with a handshake by sending a request to the target network address:

Parameter Description
crypt Null/None, only used in respones
crypt_supported An array of connection encryption methods supported by the client
fileserver_port The client's fileserver port
onion (Only used on tor) The client's onion address
protocol The protocol version the client uses (v1 or v2)
port_opened The client's client port open status
peer_id (Not used on tor) The client's peer_id
rev The client's revision number
version The client's version
target_ip The server's network address

The target initialize the encryption on the socket based on crypt_supported, then return:

Return key Description
crypt The encryption to use
crypt_supported An array of connection encryption methods supported by the server
fileserver_port The server's fileserver port
onion (Only used on tor) The server's onion address
protocol The protocol version the server uses (v1 or v2)
port_opened The server's client port open status
peer_id (Not used on tor) The server's peer_id
rev The server's revision number
version The server's version
target_ip The client's network address

Note: No encryption used on .onion connections, as the Tor network provides the transport security by default. Note: You can also implicitly initialize SSL before the handshake if you can assume it supported by remote client.

Example:

Sent handshake:

{
  "cmd": "handshake",
  "req_id": 0,
  "params": {
    "crypt": None,
    "crypt_supported": ["tls-rsa"],
    "fileserver_port": 15441,
    "onion": "zp2ynpztyxj2kw7x",
    "protocol": "v2",
    "port_opened": True,
    "peer_id": "-ZN0056-DMK3XX30mOrw",
    "rev": 2122,
    "target_ip": "192.168.1.13",
    "version": "0.5.6"
  }
}

Return:

{
 "protocol": "v2",
 "onion": "boot3rdez4rzn36x",
 "to": 0,
 "crypt": None,
 "cmd": "response",
 "rev": 2092,
 "crypt_supported": [],
 "target_ip": "zp2ynpztyxj2kw7x.onion",
 "version": "0.5.5",
 "fileserver_port": 15441,
 "port_opened": False,
 "peer_id": ""
}

Peer requests

getFile site, inner_path, location, [file_size]

Request a file from the client

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
inner_path File path relative to site directory
location Request file from this byte (max 512 bytes got sent in a request, so you need multiple requests for larger files)
file_size Total size of the requested file (optional)

Return:

Return key Description
body The requested file content
location The location of the last byte sent
size Total size of the file

ping

Checks if the client is still alive

Return:

Return key Description
body Pong

pex site, peers, need

Exchange peers with the client. Peers packed to 6 bytes (4byte IP using inet_ntoa + 2byte for port)

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
peers List of peers that the requester has (packed)
need Number of peers the requester want

Return:

Return key Description
peers List of IPv4 peers he has for the site (packed)
peers_onion List of Tor Onion Serivces peers for this site (packed)

Each element in the peers list is a packed IPv4 address.

IP address Port
4 bytes 2 bytes

Each element in the peers_onion list is a packed Tor Onion Service address.

B32-decoded onion address Port
binary_str[0:-2] binary_str[-2:]

To restore the onion address, pass the first part through base64.b32encode and append .onion to the return value.


update site, inner_path, body, [diffs]

Update a site file.

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
inner_path File path relative to site directory
body Full content of the updated content.json
diffs (optional) Diff opcodes for the modified files in the content.json

Return:

Return key Description
ok Thanks message on successful update :)
Diffs format

A dict that contains the modifications

  • Key: changed file's relative path to content.json (eg.: data.json)
  • Value: The list of diff opcodes for the file (eg.: [['=', 5], ['+', '\nhello new line'], ['-', 6]])
Possible diff opcodes:
Opcode Description
['=', number of same characters] Have not changed part of the file (eg.: ['=', 5])
['+', new text] Added characters (eg.: ['+', '\nhello new line'])
['-', number of removed characters] Full content of the updated file (eg.: ['-', 6])

After the update received, the client tries to patch the files using the diffs. If it failes to match the sha hash provided by the content.json (had different version of the file) it automatically re-downloads the whole file from the sender of the update.

Note: The patches are limited to 30KB per file and only used for .json files


listModified site, since

Lists the content.json files modified since the given parameter. It used to fetch the site's user submitted content.

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
since List content.json files since this timestamp.

Return:

Return key Description
modified_files Key: content.json inner_path
Value: last modification date

Example:

> zeronet.py --silent peerCmd 127.0.0.1 15441 listModified "{'site': '1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8', 'since': 1497507030}"
{
  "to": 1,
  "cmd": "response",
  "modified_files": {
    "data/users/1NM9k7VJfrb1UWw5agAvyRfSn3ws1wTJ5U/content.json": 1497579272,
    "data/users/1QEfmMwKVxgR4rkREbdJYjgUmF3Zy8pwHt/content.json": 1497565986,
    "data/users/16NS3rBdW9zpLmLSQoD8nLTtNVsRFtVBhd/content.json": 1497575039,
    "data/users/1CjXarXgvcNeCJ2nMQxUi4DRFWp3GEur2W/content.json": 1497513808,
    "data/users/1L5rGDgTs4W2V7gekSvJNhKa7XaHkVwotD/content.json": 1497615798,
    "data/users/1LWuc6JBhUGrKEAh1aPrPU85dEMcKmg3pS/content.json": 1497594716,
    "data/users/1KdnTJVBGzEZrJppFZtzfG9chukuMv8xSb/content.json": 1497584640,
    "data/users/1GMNmr2bDPbT4c8yVnyCoDHke52CNCdqAa/content.json": 1497614188,
    "data/users/1GRm9rED83Tkfi3iWS9m3LWHiRpPZehWLd/content.json": 1497827772,
    "data/users/12Ugp53jiMdvj1Kxa1w7c2LcXUBdGPs1oK/content.json": 1497692901,
    "data/users/1F6BMqittjWUStzUbRXm2kG2GQ3RdBLqFQ/content.json": 1497571485,
    "data/users/1GgNo3CmxPd7n2pMSF3uyqf1XHvgtTUqCe/content.json": 1497560829,
    "data/users/16nArdxrSaNThNp83kL8E6NLL9WD98iUne/content.json": 1497627929,
    "data/users/16CAJkbfNRxNJq4aKdrZ2MSYFfFGvQ8JPi/content.json": 1497664899,
    "data/users/1DrBS2sTD3BX5BBxG8eqYsxXSvGt9kc5HE/content.json": 1497632000,
    "data/users/19sggoAZ4hcorrrfWoFWP9rwfpVsL29cnZ/content.json": 1497928134,
    "data/users/1NYpJupegoTXL4cFpkNdLNJ4XaAhTNhPe1/content.json": 1497535771,
    "data/users/1R67TfYzNkCnh89EFfGmXn5LMb4hXaMRQ/content.json": 1497691787,
    "data/users/1C9HXUYFSVafLxanwkaFPZRcRgCEGsj2Cn/content.json": 1497572833,
    "data/users/1LgoHzNGWeijeZbJ8a1YgGjMCnjaM4BWG/content.json": 1497620232,
    "content.json": 1497623639
  }
}

getHashfield site

Get the client's downloaded optional file ids.

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)

Return:

Return key Description
hashfield_raw Optional file ids encoded using array.array("H", [1000, 1001..]).tostring()

Example:

> zeronet.py --silent peerCmd 192.168.1.13 15441 getHashfield "{'site': '1Gif7PqWTzVWDQ42Mo7np3zXmGAo3DXc7h'}
{
  'to': 1,
  'hashfield_raw': 'iG\xde\x02\xc6o\r;...',
  'cmd': 'response'
}

setHashfield site, hashfield_raw

Set the list of optional file ids that the requester client has.

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
hashfield_raw Optional file ids encoded using array.array("H", [1000, 1001..]).tostring()

Return:

Return key Description
ok Updated

findHashIds site, hash_ids

Queries if the client know any peer that has the requested hash_ids

Parameter Description
site Site address (example: 1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr)
hash_ids List of optional file ids the client currently looking for

Return:

Return key Description
peers Key: Optional file id
Value: List of ipv4 peers encoded using socket.inet_aton(ip) + struct.pack("H", port)
peers_onion Key: Optional file id
Value: List of onion peers encoded using base64.b32decode(onion.replace(".onion", "").upper()) + struct.pack("H", port)

Example:

> zeronet.py --silent peerCmd 192.168.1.13 15441 findHashIds "{'site': '1Gif7PqWTzVWDQ42Mo7np3zXmGAo3DXc7h', 'hash_ids': [59948, 29811]}"
{
  'to': 1,
  'peers': {
    29811: [
      'S&9\xd3Q<',
      '>f\x94\x98N\xa4',
      'gIB\x90Q<',
      '\xb4\xady\xf7Q<'
    ],
    59948: [
      'x\xcc>\xf6Q<',
      'S\xa1\xddkQ<',
      '\x05\xac\xe8\x8dQ<',
      '\x05\xc4\xe1\x93Q<',
      'Q\x02\xed\nQ<'
    ]
  },
  'cmd': 'response',
  'peers_onion': {
    29811: ['\xc7;A\xce\xbc\xd9O\xe2w<Q<'],
    59948: ['\xc7;A\xce\xbc\xd9O\xe2w<Q<']
  }
}
Optional file id

Integer representation of the first 4 character of the hash:

>>> int("ea2c2acb30bd5e1249021976536574dd3f0fd83340e023bb4e78d0d818adf30a"[0:4], 16)
59948

checkport port

Check requested port of the other peer.

Parameter Description
port Port which will be checked.

Return:

Return key Description
status Status of the port ("open" or "closed")
ip_external External IP of the requestor

Bigfile Plugin

getPieceFields site

Returns all big file piecefield that client has for that site in a dict.

Parameter Description
site Requested site

Return:

Return key Description
piecefields_packed Key: Bigfile's sha512/256 merkle root hash
Value: Packed piecefield

setPieceFields site, piecefields_packed

Set the client's piecefields for that site.

Parameter Description
site Requested site
piecefields_packed Key: Bigfile's sha512/256 merkle root hash
Value: Packed piecefield

Return:

Return key Description
ok Updated
Bigfile piecefield

Holds the the big files downloaded pieces information in a simple string with 1/0 values. (1 = Downloaded, 0 = Not downloaded)

Example: 1110000001 means the file is sized 9-10MB and the client downloaded the first 3MB and the last 1MB at 1MB piecesize.

Packed format:

Turns the string to an list of int by counting the repeating characters starting with 1.

Example: 1110000001 to [3, 6, 1], 0000000001 to [0, 9, 1], 1111111111 to [10]

After the conversion it turns it to more efficient typed array using array.array('H', piecefield)

Bigfile merkle root

During the big file hashing procedure, in addition to storing the per-piece sha512/256 hash digests in the piecemap file, the algorithm also calculates the SHA-512/256 merkle root of the file using the merkle-tools implementation. The merkle root is only used as an ID to identify the big file, not (yet) for verifying the pieces.

Note: The merkle root is chosen to identify the file, instead of the file's actual SHA-512/256 hash. Obviously, using the latter results in hashing the same file twice. (once for piecemap once for the whole file)

Note: The merkle root is not used to verify the integrity of the pieces or the big file, because doing so would take more bandwidth and space to transfer and store the merkle-proofs for partial verification, than the per-piece hash map file itself.

Bigfile piecemap

It holds the per-piece SHA-512/256 hashes. The piece size and the picemap filename is defined in content.json, eg.:

...
 "files_optional": {
  "bigfile.mp4": {
   "piece_size": 1048576,
   "piecemap": "bigfile.mp4.piecemap.msgpack",
   "sha512": "d1f0d150e1e73bb1e684d370224315d7ba21e656189eb646ef7cc394d033bc2b",
   "size": 42958831
  },
...

Having the following data structure, the piecemap file is packed into the msgpack format:

{
  b'bigfile.mp4': {b'sha512_pieces': [
    b"e\xde\x0fx\xec\xc5LZ9\x0e\xe7\x85E\x1b\xd5\xe4C'\xe7req\xe3<\xff\\\xbb\xc8b\xc2\xc1\x8e",
    b'\xef\xe8\xed\xfe\x16/\x96\xdb;;\x06n[8_\x06\x9ak|\xe1\x9f\xe1\xaf\x87\x96\xdd\xfd\x9bEf\xd9!',
    b'\x1c\xd6-\x1f\xce\xde{\xcd\x01\x93un =D\x0brmB-\xd1\x8c\xbf\xfe\xca\x8a\x1c\xf60\xbb\xedD',
    b'\x1aQdF\xd2\xbc\xdff{\xb7\x89\xf2\xd3\r\xa9\xe1\xefA-V\x18\xa4\xc8e\x13\x88v\x13\\&\xfbW',
    ...
  ]}
}