Недавно в 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]


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


Добавляем 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:


<b><i><u>Привет мир</u></i></b>

Аналогично по инструкции выше получаем идентификатор контента в 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(0x01)
  2. тип содержимого DAG (0x70)
  3. хеш тривиальный (0x00)
  4. размер данных 69 байт (0x45)

0x"01 70 00 45 123F0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18000A020801"

Конвертируем в 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 идентификатора поля. Часто идентификатор занимает один байт так как его общее значение меньше 0x80. В нашем случае первый байт 0x12. Младшие 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 
      = "?<b><i><u>Привет мир</u></i></b>"
  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 
      = "?<b><i><u>Привет мир</u></i></b>"
  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 
      = "?<b><i><u>Привет мир</u></i></b>"
  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(0x12) повторяется столько раз сколько файлов надо поместить в блок.


Для проверки добавим в начале "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


Заключение


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

Комментарии (6)


  1. firedragon
    12.09.2018 17:51

    Был бы я жителем маленькой планеты вблизи Бельзерганца, я бы послал вас с вашим понятным объяснением куда подальше. Короче где сиськи и письки как на Вояджере?

    image


    1. ivan386 Автор
      12.09.2018 18:05

      Опять забыл плашку добавить для тех кто не в теме. Добавил в начале статьи.


    1. Kain_Haart
      14.09.2018 15:32
      +1

      Ох уж это объединение хабра и гиктаймс :)


  1. Azan
    13.09.2018 05:33

    IPFS — все же это круто!
    ipfs будущее интернета (© Azan)
    Надо объединить людей связанных с (i2p, ipfs, mesh, tor, cjdns) и подобных проектов и построить новый интернет.


    1. ivan386 Автор
      13.09.2018 07:41

      Интернет непрерывно развивается. Все эти технологии это новые уровни и слои которые добавляют новые возможности в глобальной сети. Этим технологиям нужна совместимость между собой и в этой части конечно нужно общение разработчиков смежных проектов.


      Далее уже пользователь выбирает какие технологии ему нужны и использует их. И тут так-же важно чтобы он знал о тех инструментах которые ему доступны.


    1. polearnik
      13.09.2018 10:14

      в i2p есть tahoe-lafs Зачем ему еще IPFS