Межпланетная файловая система — тривиальный хеш (identity), DAG блок и Protocol Buffers

Недавно в IPFS добавили поддержу тривиального (identity) хеша. В своей статье я расскажу о нём и покажу как его можно использовать.


Напомню: InterPlanetary File System — это новая децентрализованная сеть обмена файлами (HTTP-сервер, Content Delivery Network). О ней я начал рассказ в статье «Межпланетная файловая система IPFS».

Обычно при хешировании проходя через хеш-функцию данные необратимо «сжимаются» и в результате получается короткий идентификатор. Этот идентификатор позволяет найти данные в сети и проверить их целостность.

Тривиальный хеш — это сами данные. Данные никак не изменяются и соответственно размер «хеша» равен размеру данных.

Тривиальный хеш выполняет ту же функцию что и Data: URL. Идентификатор контента в этом случае содержит сами данные вместо хеша. Это позволяет вкладывать дочерние блоки в родительский делая их доступными сразу после получения родительского. Также можно включать данные сайта непосредственно в DNS запись.

Для примера закодируем текстовую строку «Привет мир» в идентификатор контета (CID) с тривиальным хешем.
image

Структура идентификатора:

[префикс основания][varint версия CID][varint тип контента][varint ID хеша][varint длинна хеша][хеш]

Начнём с конца.


[хеш]

Тривиальный хеш в нашем случае это сама строка. Переведём её в HEX.

"Привет мир" = 0x"D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"

Это HEX этой строки в кодировке utf-8. Но чтоб браузер знал наверняка что это utf-8 строка добавим к ней в начале: 0xEFBBBF. Это маркер последовательности байтов (BOM).

0x"EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"


[varint длинна хеша]

Теперь мы можем посчитать длину «хеша». Каждые два символа HEX это один байт. Соответственно длинна получившейся строки 22 байта. В HEX это будет 0x16.

Добавляем 0x16 в начало строки.

0x"16 EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"


[varint ID хеша]

Теперь нам нужен идентификатор хеша. Тривиальный хеш или identity в таблице хешей имеет идентификатор 0x00.

Добавляем 0x00 в начало строки.

0x"00 16 EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"

Это уже мультихеш часть идентификатора можно перекодировать HEX в Base58 и мультихеш готов. Но ipfs его не распознает вне идентификатора контента (CID).

Идём дальше.


[varint тип контента]

Теперь заглянем в таблицу multicodec для того чтобы получить тип контента. В нашем случае это сырые (raw) данные и идентификатор соответственно 0x55.

Добавляем 0x55 в начало строки.

0x"55 00 16 EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"


[varint версия CID]

Мы кодируем в формат первой версии идентификатора контента. Поэтому добавляем 0×01.

Добавляем 0x01 в начало строки.

0x"01 55 00 16 EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180"

И так мы уже на финишной прямой.


[префикс основания]

Он указывает какой вариант кодирования бинарных данных в текст использован.


HEX (F)

Мы можем использовать напрямую HEX сроку добавив в начале префикс основания HEX символ «F»

F01550016EFBBBFD09FD180D0B8D0B2D0B5D18220D0BCD0B8D180

Мы получили HEX идентификатор контента который содержит utf-8 строку: «Привет мир»

Тестируем: /ipfs/F01550016EFBBBFD09FD180D0B8D0B2D0B5D18220D0BCD0B8D180


Base58btc (z)

Base58btc будет по короче поэтому

Нашу HEX строку мы переводим в base58btc. Можно воспользоваться онлайн конвертером.

0x"01 55 00 16 EFBBBF D09F D180 D0B8 D0B2 D0B5 D182 20 D0BC D0B8 D180" = "3NDGAEgXCxbPucFFCQc9s5ScqZjqVFNr56P" (base58btc)

Добавляем в начале к полученной строке символ префикс основания base58btc «z»

z3NDGAEgXCxbPucFFCQc9s5ScqZjqVFNr56P

Мы получили base58btc идентификатор контента который содержит utf-8 строку: «Привет мир»

Тестируем: /ipfs/z3NDGAEgXCxbPucFFCQc9s5ScqZjqVFNr56P


DAG блок

Текст это хорошо, но для того чтоб закодировать HTML страницу нам надо вложить её данные в DAG блок директории.

Вот наш HTML:

Привет мир

Аналогично по инструкции выше получаем идентификатор контента в base58btc для этого текста:

zeExnPvBXdTRwCBhfkJ1fHFDaXpdW4ghvQjfaCRHYxtQnd3H4w1MPbLczSqyCqVo

Теперь пишем JSON файл:

{
    "links": [{
        "Cid": {
            "/": "zeExnPvBXdTRwCBhfkJ1fHFDaXpdW4ghvQjfaCRHYxtQnd3H4w1MPbLczSqyCqVo"
        },
        "Name": "index.html"
    }],
    "data": "CAE="
}


  1. В «data» указан тип DAG блока — каталог.
  2. «links» это массив ссылок на файлы.
  3. «Name» это соответственно имя файла.
  4. «Cid» содержит идентификатор контента

Командой ipfs dag put -f"protobuf" конвертируем JSON в DAG блок через IPFS.

Я получил мультихеш: QmXXixn4rCzGguhxQPjXQ8Mr5rdqwZfJTKkeB6DfZLt8EZ

На этом этапе мы получили блок в котором каталог с одним файлом вписанным в блок.

Далее используя этот мультихеш выгружаем готовый блок

ipfs block get QmXXixn4rCzGguhxQPjXQ8Mr5rdqwZfJTKkeB6DfZLt8EZ > block.dag

Переводим содержимое block.dag в HEX:

0x"123F0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18000A020801"

Добавляем:


  1. версию CID (0×01)
  2. тип содержимого DAG (0×70)
  3. хеш тривиальный (0×00)
  4. размер данных 69 байт (0×45)
0x"01 70 00 45 123F0A2F0155202BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18200A020801"

Конвертируем в Base58btc и добавляем префикс «z»

z6S3Z3W1zuRxio8AJC41jRTdyU9pZWnU6sNbvyGyypEdD8JVNdW42ZmGYWKWGbVDELLvJNWcMspaZMUPZKt7JQmhdyXCqq7j37GL

Таким образом мы получили идентификатор контента с каталогом в котором html страница index.html с текстом «Привет мир».

Тестируем: /ipfs/z6S3Z3W1zuRxio8AJC41jRTdyU9pZWnU6sNbvyGyypEdD8JVNdW42ZmGYWKWGbVDELLvJNWcMspaZMUPZKt7JQmhdyXCqq7j37GL/

Далее этот хеш также можно вложить в другой блок либо записать в DNS dnslink запись. Так в одном блоке можно уместить небольшой простенький сайт.


DAG блок и Protocol Buffers

DAG блок можно также собрать в ручную. DAG блок это данные в формате Protocol Buffers. Верхний слой это merkledag.proto у которого в Data находится unixfs.proto.


Protocol Buffers

Любой протобуфер начинается с varint идентификатора поля. Часто идентификатор занимает один байт так как его общее значение меньше 0×80. В нашем случае первый байт 0×12. Младшие 3 бита этого поля это тип. Остальное ID заданный в proto файле.


Length-delimited

Расшифровываем идентификатор:

0x12 & 0x07 = 2 (Тип: Length-delimited)
0x12 >> 3 = 2 (ID: 2)

Length-delimited означает что дальше следует varint размер поля в байтах и непосредственно его содержимое. Этот тип используется как для различных вложенных структур так и сырых данных (string, bytes, embedded messages, packed repeated fields). Что в нём определяет уже proto файл.


Varint

Расшифруем идентификатор другого типа:

0x18 & 0x07 = 0 (Тип: Varint)
0x12 >> 3 = 3 (ID: 3)

Varint означает что дальше следует сразу значение в varint. Этот контейнер используется для записи многих типов значений (int32, int64, uint32, uint64, sint32, sint64, bool, enum). Что в нём также определяет proto файл.


Разберём block.dag который мы перевели в HEX выше

Для разбора блока можно воспользоваться сайтом который автоматически разберёт любой Protocol Buffer без использования proto файлов.

0x"123F0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18000A020801"

Разбираем блок и сопоставляем идентификаторам из proto файлов.


merkledag.proto
// An IPFS MerkleDAG Link
message PBLink {

  // multihash of the target object
  optional bytes Hash = 1;

  // utf string name. should be unique per object
  optional string Name = 2;

  // cumulative size of target object
  optional uint64 Tsize = 3;
}

// An IPFS MerkleDAG Node
message PBNode {

  // refs to other objects
  repeated PBLink Links = 2;

  // opaque user data
  optional bytes Data = 1;
}


unixfs.proto
message Data {
    enum DataType {
        Raw = 0;
        Directory = 1;
        File = 2;
        Metadata = 3;
        Symlink = 4;
        HAMTShard = 5;
    }

    required DataType Type = 1;
    optional bytes Data = 2;
    optional uint64 filesize = 3;
    repeated uint64 blocksizes = 4;

    optional uint64 hashType = 5;
    optional uint64 fanout = 6;
}
12 (Тип: 2 (Length-delimited). ID: 2 (PBLink PBNode.Links (merkledag.proto)))
 3F (Размер: 63 байта)
  0A (Тип: 2 (Length-delimited). ID: 1 (PBLink.Hash))
   2F (Размер: 47 байта)
    01 55 00 2B (CIDv1 Raw Identity 43 байта)
     EFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E 
      = "Привет мир"
  12 (Тип: 2 (Length-delimited). ID: 2 (PBLink.Name))
   0A (Размер: 10 байт)
    696E6465782E68746D6C = "index.html"
  18 (Тип: 0 (Varint). ID: 3 (PBLink.Size))
   00 (Значение: 0)
0A (Тип: 2 (Length-delimited). ID: 1 (PBNode.Data = Data (unixfs.proto)))
 02 (Размер: 2 байт)
  08 (Тип: 0 (Varint). ID: 1 (Data.Type))
   01 (1 == Data.DataType.Directory)

Соответственно блок с двумя фалами будет выглядеть так:

12 (Тип: 2 (Length-delimited). ID: 2 (PBLink PBNode.Links (merkledag.proto)))
 3B (Размер: 59 байт)
  0A (Тип: 2 (Length-delimited). ID: 1 (PBLink.Hash))
   2F (Размер: 47 байта)
    01 55 00 2B (CIDv1 Raw Identity 43 байта)
     EFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E 
      = "Привет мир"
  12 (Тип: 2 (Length-delimited). ID: 2 (PBLink.Name))
   06 (Размер: 6 байт)
    312E68746D6C = "1.html"
  18 (Тип: 0 (Varint). ID: 3 (PBLink.Size))
   00 (Значение: 0)
12 (Тип: 2 (Length-delimited). ID: 2 (PBLink PBNode.Links))
 3B (Размер: 59 байт)
  0A (Тип: 2 (Length-delimited). ID: 1 (PBLink.Hash))
   2F (Размер: 47 байта)
    01 55 00 2B (CIDv1 Raw Identity 43 байта)
     EFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E 
      = "Привет мир"
  12 (Тип: 2 (Length-delimited). ID: 2 (PBLink.Name))
   06 (Размер: 6 байт)
    322E68746D6C = "2.html"
  18 (Тип: 0 (Varint). ID: 3 (PBLink.Size))
   00 (Значение: 0)
0A (Тип: 2 (Length-delimited). ID: 1 (PBNode.Data = Data(unixfs.proto)))
 02 (Размер: 2 байт)
  08 (Тип: 0 (Varint). ID: 1 (Data.Type))
   01 (1 == Data.DataType.Directory)

То есть поле PBNode.Links (0×12) повторяется столько раз сколько файлов надо поместить в блок.

Для проверки добавим в начале «F 01 70 00» (HEX CIDv1 DAG Identity) и размер DAG блока »7E»(126 байт)

F 01 70 00 7E
12 3B 0A 2F 01 55 00 2B EFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E 12 06 312E68746D6C 18 00
12 3B 0A 2F 01 55 00 2B EFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E 12 06 322E68746D6C 18 00
0A 02 08 01

Проверяем: /ipfs/F0170007E123B0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E1206312E68746D6C1800123B0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E1206322E68746D6C18000A020801


Заключение

Надеюсь достаточно дал информации для того чтобы возможно было реализовать создание блоков и идентификаторов.

© Habrahabr.ru