Межпланетная файловая система — тривиальный хеш (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]


    Мы кодируем в формат первой версии идентификатора контента. Поэтому добавляем 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


    Заключение


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

    • +19
    • 4,1k
    • 6
    Поделиться публикацией

    Похожие публикации

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

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

      image
        +1

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

          +1

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

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

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


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

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

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое