2011-05-25 04:26:07 +02:00
|
|
|
============================================
|
|
|
|
BitTorrent extension for arbitrary DHT store
|
|
|
|
============================================
|
|
|
|
|
|
|
|
:Author: Arvid Norberg, arvid@rasterbar.com
|
2012-11-18 05:32:22 +01:00
|
|
|
:Version: 1.0.0
|
2011-05-25 04:26:07 +02:00
|
|
|
|
|
|
|
.. contents:: Table of contents
|
|
|
|
:depth: 2
|
|
|
|
:backlinks: none
|
|
|
|
|
|
|
|
This is a proposal for an extension to the BitTorrent DHT to allow
|
|
|
|
storing and retrieving of arbitrary data.
|
|
|
|
|
|
|
|
It supports both storing *immutable* items, where the key is
|
|
|
|
the SHA-1 hash of the data itself, and *mutable* items, where
|
|
|
|
the key is the public key of the key pair used to sign the data.
|
|
|
|
|
|
|
|
There are two new proposed messages, ``put`` and ``get``.
|
|
|
|
|
|
|
|
terminology
|
|
|
|
-----------
|
|
|
|
|
|
|
|
In this document, a *storage node* refers to the node in the DHT to which
|
2013-08-18 18:01:20 +02:00
|
|
|
an item is being announced and stored on. A *requesting node* refers to
|
2011-05-25 04:26:07 +02:00
|
|
|
a node which makes look-ups in the DHT to find the storage nodes, to
|
|
|
|
request items from them, and possibly re-announce those items to keep them
|
|
|
|
alive.
|
|
|
|
|
|
|
|
messages
|
|
|
|
--------
|
|
|
|
|
|
|
|
The proposed new messages ``get`` and ``put`` are similar to the existing ``get_peers``
|
|
|
|
and ``announce_peer``.
|
|
|
|
|
2013-08-18 18:01:20 +02:00
|
|
|
Responses to ``get`` should always include ``nodes`` and ``nodes6``. Those fields
|
|
|
|
have the same semantics as in its ``get_peers`` response. It should also include a write token,
|
|
|
|
``token``, with the same semantics as int ``get_peers``. The write token MAY be tied
|
|
|
|
specifically to the key which ``get`` requested. i.e. the ``token`` can only be used
|
2013-12-31 09:37:42 +01:00
|
|
|
to store values under that one key. It may also be tied to the node ID and IP
|
|
|
|
address of the requesting node.
|
2011-05-25 04:26:07 +02:00
|
|
|
|
|
|
|
The ``id`` field in these messages has the same semantics as the standard DHT messages,
|
|
|
|
i.e. the node ID of the node sending the message, to maintain the structure of the DHT
|
|
|
|
network.
|
|
|
|
|
|
|
|
The ``token`` field also has the same semantics as the standard DHT message ``get_peers``
|
|
|
|
and ``announce_peer``, when requesting an item and to write an item respectively.
|
|
|
|
|
2013-11-29 07:06:07 +01:00
|
|
|
The ``k`` field is the 32 byte curve25519 public key, which the signature
|
2012-11-16 23:25:39 +01:00
|
|
|
can be authenticated with. When looking up a mutable item, the ``target`` field
|
2013-12-31 09:37:42 +01:00
|
|
|
MUST be the SHA-1 hash of this key concatenated with the ``salt``, if present.
|
2012-11-16 23:25:39 +01:00
|
|
|
|
2011-05-25 04:26:07 +02:00
|
|
|
The distinction between storing mutable and immutable items is the inclusion
|
2013-12-31 09:37:42 +01:00
|
|
|
of a public key, a sequence number, signature and an optional salt (``k``,
|
|
|
|
``seq``, ``sig`` and ``salt``).
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2012-11-16 23:25:39 +01:00
|
|
|
``get`` requests for mutable items and immutable items cannot be distinguished from
|
|
|
|
eachother. An implementation can either store mutable and immutable items in the same
|
|
|
|
hash table internally, or in separate ones and potentially do two lookups for ``get``
|
|
|
|
requests.
|
|
|
|
|
|
|
|
The ``v`` field is the *value* to be stored. It is allowed to be any bencoded type (list,
|
2011-05-25 04:26:07 +02:00
|
|
|
dict, string or integer). When it's being hashed (for verifying its signature or to calculate
|
2013-12-31 09:37:42 +01:00
|
|
|
its key), its flattened, bencoded, form is used. It is important to use the verbatim
|
2012-12-14 04:03:18 +01:00
|
|
|
bencoded representation as it appeared in the message. decoding and then re-encoding
|
|
|
|
bencoded structures is not necessarily an identity operation.
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2013-12-31 09:37:42 +01:00
|
|
|
Storing nodes MAY reject ``put`` requests where the bencoded form of ``v`` is longer
|
|
|
|
than 1000 bytes. In other words, it's not safe to assume storing more than
|
|
|
|
1000 bytes will succeed.
|
2011-05-25 04:26:07 +02:00
|
|
|
|
|
|
|
immutable items
|
|
|
|
---------------
|
|
|
|
|
|
|
|
Immutable items are stored under their SHA-1 hash, and since they cannot be modified,
|
|
|
|
there is no need to authenticate the origin of them. This makes immutable items simple.
|
|
|
|
|
2012-12-14 04:03:18 +01:00
|
|
|
A node making a lookup SHOULD verify the data it receives from the network, to verify
|
|
|
|
that its hash matches the target that was looked up.
|
|
|
|
|
2011-05-25 04:26:07 +02:00
|
|
|
put message
|
|
|
|
...........
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"a":
|
|
|
|
{
|
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
2013-08-18 18:01:20 +02:00
|
|
|
"v": *<any bencoded type, whose encoded size <= 1000>*
|
2011-05-25 04:26:07 +02:00
|
|
|
},
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "q",
|
|
|
|
"q": "put"
|
|
|
|
}
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"r": { "id": *<20 byte id of sending node (string)>* },
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "r",
|
|
|
|
}
|
|
|
|
|
|
|
|
get message
|
|
|
|
...........
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"a":
|
|
|
|
{
|
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
|
|
|
"target": *<SHA-1 hash of item (string)>*,
|
|
|
|
},
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "q",
|
|
|
|
"q": "get"
|
|
|
|
}
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"r":
|
|
|
|
{
|
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
|
|
|
"token": *<write token (string)>*,
|
|
|
|
"v": *<any bencoded type whose SHA-1 hash matches 'target'>*,
|
2012-11-09 03:52:27 +01:00
|
|
|
"nodes": *<IPv4 nodes close to 'target'>*,
|
2011-05-25 04:26:07 +02:00
|
|
|
"nodes6": *<IPv6 nodes close to 'target'>*
|
|
|
|
},
|
|
|
|
"t": *<transaction-id>*,
|
|
|
|
"y": "r",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mutable items
|
|
|
|
-------------
|
|
|
|
|
|
|
|
Mutable items can be updated, without changing their DHT keys. To authenticate
|
|
|
|
that only the original publisher can update an item, it is signed by a private key
|
2012-11-16 23:25:39 +01:00
|
|
|
generated by the original publisher. The target ID mutable items are stored under
|
|
|
|
is the SHA-1 hash of the public key (as it appears in the ``put`` message).
|
2011-05-25 04:26:07 +02:00
|
|
|
|
|
|
|
In order to avoid a malicious node to overwrite the list head with an old
|
|
|
|
version, the sequence number ``seq`` must be monotonically increasing for each update,
|
|
|
|
and a node hosting the list node MUST not downgrade a list head from a higher sequence
|
2012-12-14 04:03:18 +01:00
|
|
|
number to a lower one, only upgrade. The sequence number SHOULD not exceed ``MAX_INT64``,
|
|
|
|
(i.e. ``0x7fffffffffffffff``. A client MAY reject any message with a sequence number
|
2013-10-31 01:57:33 +01:00
|
|
|
exceeding this. A client MAY also reject any message with a negative sequence number.
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2013-11-29 07:06:07 +01:00
|
|
|
The signature is a 64 byte curve25519 signature of the bencoded sequence
|
2013-12-31 09:37:42 +01:00
|
|
|
number concatenated with the ``v`` key. e.g. something like this::
|
|
|
|
|
|
|
|
3:seqi4e1:v12:Hello world!
|
|
|
|
|
|
|
|
If the ``salt`` key is present and non-empty, the salt string must be included
|
|
|
|
in what's signed. Note that if ``salt`` is specified and an empty string, it
|
|
|
|
is as if it was not specified and nothing in addition to the sequence number
|
|
|
|
and the data is signed.
|
|
|
|
|
|
|
|
When a salt is included in what is signed, the key ``salt`` with the value
|
|
|
|
of the key is prepended in its bencoded form. For example, if ``salt`` is
|
|
|
|
"foobar", the buffer to be signed is::
|
|
|
|
|
|
|
|
4:salt6:foobar3:seqi4e1:v12:Hello world!
|
2011-05-25 04:26:07 +02:00
|
|
|
|
|
|
|
put message
|
|
|
|
...........
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"a":
|
|
|
|
{
|
2013-09-03 02:45:48 +02:00
|
|
|
"cas": *<optional 20 byte hash (string)>*,
|
2011-05-25 04:26:07 +02:00
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
2013-11-29 07:06:07 +01:00
|
|
|
"k": *<curve25519 public key (32 bytes string)>*,
|
2013-12-31 09:37:42 +01:00
|
|
|
"salt": *<optional salt to be appended to "k" when hashing (string)>*
|
2011-05-25 04:26:07 +02:00
|
|
|
"seq": *<monotonically increasing sequence number (integer)>*,
|
2013-11-29 07:06:07 +01:00
|
|
|
"sig": *<curve25519 signature (64 bytes string)>*,
|
2011-05-25 04:26:07 +02:00
|
|
|
"token": *<write-token (string)>*,
|
2013-08-18 18:01:20 +02:00
|
|
|
"v": *<any bencoded type, whose encoded size < 1000>*
|
2011-05-25 04:26:07 +02:00
|
|
|
},
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "q",
|
|
|
|
"q": "put"
|
|
|
|
}
|
|
|
|
|
2013-08-18 18:01:20 +02:00
|
|
|
Storing nodes receiving a ``put`` request where ``seq`` is lower than or equal
|
|
|
|
to what's already stored on the node, MUST reject the request. If the sequence
|
|
|
|
number is equal, and the value is also the same, the node SHOULD reset its timeout
|
|
|
|
counter.
|
|
|
|
|
2013-09-03 02:45:48 +02:00
|
|
|
If the sequence number in the ``put`` message is lower than the sequence number
|
|
|
|
associated with the currently stored value, the storing node MAY return an error
|
|
|
|
message with code 302 (see error codes below).
|
|
|
|
|
2013-08-18 18:01:20 +02:00
|
|
|
Note that this request does not contain a target hash. The target hash under
|
|
|
|
which this blob is stored is implied by the ``k`` argument. The key is
|
|
|
|
the SHA-1 hash of the key (``k``).
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2013-12-31 09:37:42 +01:00
|
|
|
In order to support a single key being used to store separate items in the DHT,
|
|
|
|
an optional ``salt`` can be specified in the ``put`` request of mutable
|
|
|
|
items. If the salt entry is not present, it can be assumed to be an empty
|
|
|
|
string, and its semantics should be identical as specifying a salt key
|
|
|
|
with an empty string. The salt can be any binary string (but probably most
|
|
|
|
conveniently a hash of something). This string is appended to the key,
|
|
|
|
as specified in the ``k`` field, when calculating the key to store the
|
|
|
|
blob under (i.e. the key ``get`` requests specify to retrieve this data).
|
|
|
|
|
|
|
|
This lets a single entity, with a single key, publish any number of unrelated
|
|
|
|
items, with a single key that readers can verify. This is useful if the
|
|
|
|
publisher doesn't know ahead of time how many different items are to be
|
|
|
|
published. It can distribute a single public key for users to authenticate
|
|
|
|
the published blobs.
|
|
|
|
|
2013-10-28 22:48:21 +01:00
|
|
|
The ``cas`` field is optional. If present it is interpreted as the sha-1 hash of
|
2013-12-31 09:37:42 +01:00
|
|
|
the sequence number, ``v`` field and possibly the ``salt`` field, that is
|
|
|
|
expected to be replaced. The buffer to hash is the same as the one signed when
|
|
|
|
storing. ``cas`` is short for *compare and swap*, it has similar semantics as
|
|
|
|
CAS CPU instructions. If specified as part of the put command, and the current
|
|
|
|
value stored under the public key differs from the expected value, the store
|
|
|
|
fails. The ``cas`` field only applies to mutable puts. If there is no current
|
|
|
|
value, the ``cas`` field SHOULD be ignored. A put operation should not be
|
|
|
|
prevented based on the ``cas`` field if no value is currently present.
|
2013-09-03 02:45:48 +02:00
|
|
|
|
2011-05-25 04:26:07 +02:00
|
|
|
Response:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"r": { "id": *<20 byte id of sending node (string)>* },
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "r",
|
|
|
|
}
|
|
|
|
|
2013-09-03 02:45:48 +02:00
|
|
|
If the store fails for any reason an error message is returned instead of the message
|
|
|
|
template above, i.e. one where "y" is "e" and "e" is a tuple of [error-code, message]).
|
|
|
|
Failures include where the ``cas`` hash mismatches and the sequence number is outdated.
|
|
|
|
|
|
|
|
If no ``cas`` field is included in the ``put`` message, the value of the current ``v``
|
|
|
|
field should be disregarded when determining whether or not to save the item.
|
2013-12-31 09:37:42 +01:00
|
|
|
(However, the signature, sequence number obviously still should).
|
2013-09-03 02:45:48 +02:00
|
|
|
|
|
|
|
The error message (as specified by BEP5_) looks like this:
|
|
|
|
|
2013-09-03 21:44:01 +02:00
|
|
|
.. _BEP5: http://www.bittorrent.org/beps/bep_0005.html
|
2013-09-03 02:45:48 +02:00
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"e": [ *<error-code (integer)>*, *<error-string (string)>* ],
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "e",
|
|
|
|
}
|
|
|
|
|
|
|
|
In addition to the error codes defined in BEP5_, this specification defines
|
|
|
|
some additional error codes.
|
|
|
|
|
|
|
|
+------------+-----------------------------+
|
|
|
|
| error-code | description |
|
|
|
|
+============+=============================+
|
|
|
|
| 205 | message (i.e. ``v`` field) |
|
|
|
|
| | too big. |
|
|
|
|
+------------+-----------------------------+
|
|
|
|
| 206 | invalid signature |
|
|
|
|
+------------+-----------------------------+
|
|
|
|
| 301 | the CAS hash mismatched, |
|
|
|
|
| | re-read value and try |
|
|
|
|
| | again. |
|
|
|
|
+------------+-----------------------------+
|
|
|
|
| 302 | sequence number less than |
|
|
|
|
| | current. |
|
|
|
|
+------------+-----------------------------+
|
|
|
|
|
|
|
|
An implementation MUST emit 301 errors if the cas-hash mismatches. This is
|
|
|
|
a critical feature in synchronization of multiple agents sharing an immutable item.
|
|
|
|
|
2011-05-25 04:26:07 +02:00
|
|
|
get message
|
|
|
|
...........
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
2012-11-09 03:52:27 +01:00
|
|
|
"a":
|
2011-05-25 04:26:07 +02:00
|
|
|
{
|
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
2013-12-31 09:37:42 +01:00
|
|
|
"target:" *<20 byte SHA-1 hash of public key and salt (string)>*
|
2011-05-25 04:26:07 +02:00
|
|
|
},
|
|
|
|
"t": *<transaction-id (string)>*,
|
2012-11-09 03:52:27 +01:00
|
|
|
"y": "q",
|
2011-05-25 04:26:07 +02:00
|
|
|
"q": "get"
|
|
|
|
}
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
.. parsed-literal::
|
|
|
|
|
|
|
|
{
|
|
|
|
"r":
|
|
|
|
{
|
|
|
|
"id": *<20 byte id of sending node (string)>*,
|
2013-11-29 07:06:07 +01:00
|
|
|
"k": *<curve25519 public key (32 bytes string)>*,
|
2012-11-09 03:52:27 +01:00
|
|
|
"nodes": *<IPv4 nodes close to 'target'>*,
|
|
|
|
"nodes6": *<IPv6 nodes close to 'target'>*,
|
2013-12-31 09:37:42 +01:00
|
|
|
"salt": *<optional salt to be appended to "k" when hashing (string)>*
|
2011-05-25 04:26:07 +02:00
|
|
|
"seq": *<monotonically increasing sequence number (integer)>*,
|
2013-11-29 07:06:07 +01:00
|
|
|
"sig": *<curve25519 signature (64 bytes string)>*,
|
2011-05-25 04:26:07 +02:00
|
|
|
"token": *<write-token (string)>*,
|
2013-08-18 18:01:20 +02:00
|
|
|
"v": *<any bencoded type, whose encoded size <= 1000>*
|
2011-05-25 04:26:07 +02:00
|
|
|
},
|
|
|
|
"t": *<transaction-id (string)>*,
|
|
|
|
"y": "r",
|
|
|
|
}
|
|
|
|
|
|
|
|
signature verification
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
In order to make it maximally difficult to attack the bencoding parser, signing and verification of the
|
|
|
|
value and sequence number should be done as follows:
|
|
|
|
|
|
|
|
1. encode value and sequence number separately
|
2013-12-31 09:37:42 +01:00
|
|
|
2. concatenate ("4:salt" *length-of-salt* ":" *salt*) "3:seqi" *seq*
|
|
|
|
"e1:v" *len* ":" and the encoded value.
|
2013-09-03 02:45:48 +02:00
|
|
|
sequence number 1 of value "Hello World!" would be converted to: "3:seqi1e1:v12:Hello World!"
|
2011-05-25 04:26:07 +02:00
|
|
|
In this way it is not possible to convince a node that part of the length is actually part of the
|
|
|
|
sequence number even if the parser contains certain bugs. Furthermore it is not possible to have a
|
|
|
|
verification failure if a bencoding serializer alters the order of entries in the dictionary.
|
2013-12-31 09:37:42 +01:00
|
|
|
The salt is in parenthesis because it is optional. It is only prepended if
|
|
|
|
a non-empty salt is specified in the ``put`` request.
|
2013-08-18 18:01:20 +02:00
|
|
|
3. sign or verify the concatenated string
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2012-11-16 23:25:39 +01:00
|
|
|
On the storage node, the signature MUST be verified before accepting the store command. The data
|
|
|
|
MUST be stored under the SHA-1 hash of the public key (as it appears in the bencoded dict).
|
|
|
|
|
2013-08-18 18:01:20 +02:00
|
|
|
On the requesting nodes, the key they get back from a ``get`` request MUST be verified to hash
|
2012-11-16 23:25:39 +01:00
|
|
|
to the target ID the lookup was made for, as well as verifying the signature. If any of these fail,
|
|
|
|
the response SHOULD be considered invalid.
|
|
|
|
|
2011-05-25 04:26:07 +02:00
|
|
|
expiration
|
|
|
|
----------
|
|
|
|
|
|
|
|
Without re-announcement, these items MAY expire in 2 hours. In order
|
|
|
|
to keep items alive, they SHOULD be re-announced once an hour.
|
|
|
|
|
2013-09-03 02:45:48 +02:00
|
|
|
Any node that's interested in keeping a blob in the DHT alive may announce it. It would simply
|
|
|
|
repeat the signature for a mutable put without having the private key.
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2013-12-31 09:37:42 +01:00
|
|
|
test vector
|
|
|
|
-----------
|
|
|
|
|
|
|
|
The buffer being signed::
|
|
|
|
|
|
|
|
3:seqi1e1:v12:Hello World!
|
|
|
|
|
|
|
|
public key::
|
|
|
|
|
|
|
|
77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
|
|
|
|
|
|
|
|
private key::
|
|
|
|
|
|
|
|
e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
|
|
|
|
b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
|
|
|
|
|
|
|
|
signature::
|
|
|
|
|
|
|
|
305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
|
|
|
|
1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01
|
|
|
|
|
|
|
|
resources
|
|
|
|
---------
|
|
|
|
|
|
|
|
Libraries that implement curve25519 DSA:
|
|
|
|
|
|
|
|
* NaCl_
|
|
|
|
* libsodium_
|
|
|
|
* `nightcracker's ed25519`_
|
2011-05-25 04:26:07 +02:00
|
|
|
|
2013-12-31 09:37:42 +01:00
|
|
|
.. _NaCl: http://nacl.cr.yp.to/
|
|
|
|
.. _libsodium: https://github.com/jedisct1/libsodium
|
|
|
|
.. _`nightcracker's ed25519`: https://github.com/nightcracker/ed25519
|
2011-05-25 04:26:07 +02:00
|
|
|
|