Недавно в IPFS добавили поддержу тривиального (identity) хеша. В своей статье я расскажу о нём и покажу как его можно использовать.
Напомню: InterPlanetary File System — это новая децентрализованная сеть обмена файлами (HTTP-сервер, Content Delivery Network). О ней я начал рассказ в статье "Межпланетная файловая система IPFS".
Обычно при хешировании проходя через хеш-функцию данные необратимо "сжимаются" и в результате получается короткий идентификатор. Этот идентификатор позволяет найти данные в сети и проверить их целостность.
Тривиальный хеш — это сами данные. Данные никак не изменяются и соответственно размер "хеша" равен размеру данных.
Тривиальный хеш выполняет ту же функцию что и Data: URL. Идентификатор контента в этом случае содержит сами данные вместо хеша. Это позволяет вкладывать дочерние блоки в родительский делая их доступными сразу после получения родительского. Также можно включать данные сайта непосредственно в DNS запись.
Для примера закодируем текстовую строку "Привет мир" в идентификатор контета(CID) с тривиальным хешем.
Структура идентификатора:
[префикс основания][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="
}
- В "data" указан тип DAG блока — каталог.
- "links" это массив ссылок на файлы.
- "Name" это соответственно имя файла.
- "Cid" содержит идентификатор контента
Командой ipfs dag put -f"protobuf"
конвертируем JSON в DAG блок через IPFS.
Я получил мультихеш: QmXXixn4rCzGguhxQPjXQ8Mr5rdqwZfJTKkeB6DfZLt8EZ
На этом этапе мы получили блок в котором каталог с одним файлом вписанным в блок.
Далее используя этот мультихеш выгружаем готовый блок
ipfs block get QmXXixn4rCzGguhxQPjXQ8Mr5rdqwZfJTKkeB6DfZLt8EZ > block.dag
Переводим содержимое block.dag в HEX:
0x"123F0A2F0155002BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18000A020801"
Добавляем:
- версию CID(0x01)
- тип содержимого DAG (0x70)
- хеш тривиальный (0x00)
- размер данных 69 байт (0x45)
0x"01 70 00 45 123F0A2F0155202BEFBBBF3C623E3C693E3C753ED09FD180D0B8D0B2D0B5D18220D0BCD0B8D1803C2F753E3C2F693E3C2F623E120A696E6465782E68746D6C18200A020801"
Конвертируем в Base58btc и добавляем префикс "z"
z6S3Z3W1zuRxio8AJC41jRTdyU9pZWnU6sNbvyGyypEdD8JVNdW42ZmGYWKWGbVDELLvJNWcMspaZMUPZKt7JQmhdyXCqq7j37GL
Таким образом мы получили идентификатор контента с каталогом в котором html страница index.html с текстом "Привет мир".
Далее этот хеш также можно вложить в другой блок либо записать в 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 файлов.
// 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;
}
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
Заключение
Надеюсь достаточно дал информации для того чтобы возможно было реализовать создание блоков и идентификаторов.
Автор: ivan386