Узнайте, как использовать DNS-серверы в качестве распределенных баз данных блокчейна.

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

Как всегда бывает в информатике, многие платформы блокчейнов управляют всей сложностью и позволяют просто сохранять данные, как если бы у нас за капотом была простая база данных.

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

Это решение может легко позволить вам иметь неизменяемые данные, которые можно проверять и надежно хранить.

Статья построена следующим образом:

  • Что такое база данных блокчейн и как она используется
  • Идея
  • Как реализовать блокчейн, используя только DNS-сервис

Начнем путешествие!

Что такое база данных блокчейн и как она используется

Как обычно, мы можем начать с определения Википедии:

Блокчейн,« [..] - это постоянно растущий список записей , называемых блоками, которые связаны с помощью криптографии . Каждый блок содержит криптографический хеш предыдущего блока, временную метку [..]. По своей конструкции блокчейн устойчив к модификации своих данных. Это связано с тем, что после записи данные в любом данном блоке не могут быть изменены задним числом без изменения всех последующих блоков.

Для использования в качестве распределенного реестра блокчейн обычно управляется« одноранговой сетью, коллективно придерживающейся протокола для межузловой связи и проверки новых блоков». - https://en.wikipedia.org/wiki/Blockchain

Другими словами, основные особенности блокчейна заключаются в том, что он:

  • хранит данные, подключая одну запись к предыдущей
  • делает так, что вы не можете изменить одну запись, не сделав несогласованной всю последовательность данных
  • хранит данные в распределенной базе данных

Итак, как создать его с нуля?

Я думал, что цепочка узлов - это более или менее связанный список, где каждый блок имеет неизменный хеш. После этого вам просто понадобится безопасная распределенная база данных для хранения данных. Что такое древняя распределенная база данных? Что ж, есть распределенная база данных, которая есть у всех, и никто не знает! Я говорю о DNS. Да, он распространяется, в нем хранятся данные. У всех есть одна служба DNS. Я понимаю, что это не та цель, для которой он был предназначен для использования, но давайте поиграем с этим.

Идея

Рабочий процесс этого протокола заключается в том, что доверенный центр записывает данные в DNS. Каждая запись имеет уникальный ключ, являющийся хешем содержимого. Это означает, что, изменяя данные, вы изменяете идентификатор, и все дочерние элементы, которые на него указывают, будут несовместимы. Более того, протокол DNS является распределенным, поэтому многие копии данных совместно используются серверами, а это означает, что один из ваших DNS будет отключен, а другой будет продолжать обслуживать данные. Учтите также, что DNS широко кэшируется, и это делает вашу коммуникацию эффективной (работа с неизменяемым кэшированием данных никогда не является проблемой).

Система использует DNS в качестве хранилища, которое уже есть у всех компаний, поэтому оно предоставляется без каких-либо дополнительных затрат. Сама DNS - это распределенная база данных.

Теперь, когда мы определили, где хранить данные, нам просто нужно понять, как это сделать. Следующим шагом является определение протокола связи, который позволяет всем сторонам играть свои роли. На следующем изображении показан поток.

На изображении выше мы имеем:

  • сущность, которая публикует в DNS. В нем есть ключ к записи - другие люди могут писать записи, но они будут непонятными.
  • потребитель, который толкает производителя и читает данные
  • данные, которые могут быть любыми данными JSON. Вы можете сделать его общедоступным или оставить его закрытым.

Я также написал полный подробный документ, который вы можете найти в конце этой статьи, который позволяет любому интегрировать его или реализовать клиента на любом языке программирования.

В следующем разделе мы увидим, как реализовать решение.

Как это реализовать

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

Для реализации блокчейна с использованием DNS мы должны столкнуться с некоторыми важными проблемами:

  • Ограничения DNS - DNS не предназначен для хранения больших объемов данных. Мы хотим использовать записи TXT, но они имеют длину всего 254 символа. Это очень серьезное ограничение, если мы стремимся хранить большой объект JSON.
  • Безопасность. Даже если мы хотим, чтобы наши данные были общедоступными, существует проблема с протоколом UDP, используемым DNS. Он не зашифрован, и нет никакого механизма сертификата, который мог бы протолкнуть авторитет, как в протоколе HTTPS.
  • Данные являются общедоступными по дизайну. Это может быть проблемой.

Для всех этих вопросов есть решение, и, как вы увидите, его очень легко реализовать. Фактически, используя криптографию и изобретательность, мы найдем умное решение для всех проблем, перечисленных выше.

Посмотрим, как это работает.

Создайте среду песочницы

Первый шаг - создать среду песочницы, в которой мы хотим играть. Для начала работы нам нужен локальный DNS-сервер с системой API. Мы достигаем этого, создавая файл docker-compose, в котором он находится. Я использовал проект Visual Studio, в котором создал веб-приложение, которое мы будем использовать для проверки данных, библиотеку, которая будет нашим ядром, и проект тестирования. Результат следующий:

При запуске docker-compose up все запускается и готово к тестированию. Что касается DNS, я использовал Shaman DNS, очень легкий и с доступными HTTP API. Он работает со следующей конфигурацией:

version: '3.4'
services:
  blockchaindns.web:
    image: ${DOCKER_REGISTRY-}blockchaindnsweb
    build:
      context: .
      dockerfile: BlockChainDNS.Web/Dockerfile
  dns:
    image: tgpfeiffer/shaman-dns  
    command: shaman --server --token xxx --api-listen 0.0.0.0:1632 --dns-listen 0.0.0.0:53 -l trace --insecure  
    ports:
      - 1632:1632
      - 53:53/udp

Здесь xxx - это токен, который вы хотите использовать для аутентификации, а DNS настроен на прием запросов от всех хостов (0.0.0.0:port).

После запуска с docker-compose up вы можете протестировать его с помощью консоли:

#create a record in Shaman DNS
curl --location --request POST 'localhost:1632/records' \
--header 'Content-Type: application/json' \
--data-raw '{
  "domain": "test.myfakedomain.it.",
  "records": [
    {
      "ttl": 60,
      "class": "IN",
      "type": "A",
      "address": "127.0.0.1"
    }
  ]
}'
#test the record
nslookup test.myfakedomain.it 127.0.0.1
#output
# Server:  UnKnown
# Address:  127.0.0.1
# Response from server:
# Nome:    test.myfakedomain.it
# Address:  127.0.0.1

Теперь, когда у нас есть работающий локальный DNS, мы можем создать клиента, который может управлять записями DNS через API.

Клиент DNS

Второй шаг - обернуть функциональные возможности DNS-клиента, которые будут использоваться в приложении. Я хотел здесь иметь возможность изменять службу DNS в будущем, поэтому я создал интерфейс и реализацию класса. Следующие фрагменты показывают интерфейс:

public interface IDNSClient
{
  Task<DNSEntry> GetRecord(string host, string zone);
  Task<bool> AddRecord(DNSEntry entry);
  Action Init { get; set; }
}

Как вы можете представить, реализация клиента выполняет HTTP-вызовы для сохранения записи. Вы можете найти полную реализацию класса в проекте GitHub, прикрепленном в конце статьи. Мы реализовали только локального провайдера для Shaman, но его легко расширить для поддержки любого коммерческого DNS на большинстве современных хостинг-провайдеров.

Сервис блокчейн

Теперь, когда у нас есть оперативная часть, пора реализовать бизнес-логику. Вся работа выполняется на стороне клиента, которая вычисляет данные для сохранения и вызывает клиентский метод DNS для сохранения записей. Уровень обслуживания состоит из двух частей:

  • BlockChainNode: представление узла
  • BlockChainService: Сервис, реализующий логику

Давайте посмотрим подробно, как работают эти классы.

BlockChainNode

Это простой класс со свойством JObject, в котором пользователь может хранить любые данные. Он вычисляет ключевые данные хеширования. Данные содержат историю, которая является ссылкой на родительский элемент. Изменение только байта данных изменит ключ, и это сделает следующие узлы несовместимыми. Следующий фрагмент кода показывает наиболее важную часть этого класса.

Наиболее важные части кода:

  • Объект данных: объект JSON, в котором пользователь может хранить данные.
  • История: наблюдаемый список, синхронизированный с данными (любое изменение в истории меняет узел _history и наоборот.)
  • Хеш: хеш, вычисленный из MD5 текстового представления данных. Результат кодируется алгоритмом Base32 - аналогично Base 64, но использует только четыре байта и содержит только символы нижнего регистра. Это связано с тем, что в DNS не учитывается регистр, а использование широко используемой кодировки Base64 приводит к несогласованным данным.

Теперь, когда у нас есть модель, мы должны перейти к следующему шагу: бизнес-логике, реализованной службой.

BlockChainService

Сервис блокчейн реализует методы сохранения, чтения и проверки записей. Труднее всего обойти ограничение в 255 символов длины записи DNS-сервера. Решение заключалось в кодировании содержимого в Base64 и последующем разделении на фрагменты, сохраняемые в разных записях с соглашением об именах. Ключ используется как часть URL-адреса. Итак, для элемента mykey.domain.dom у нас будет 0.mykey.domain.dom, 1.mykey.domain.dom и т. Д. В следующем фрагменте кода показан метод сохранения.

Как видно из предыдущего фрагмента, вызывающего WriteDNSFragmentedText, вводимый текст разделяется, и данные сохраняются во многих записях DNS.

При чтении данных все наоборот. Я пытаюсь получить подзапись 0,1,2 и так далее, пока не появятся данные. Как только я собрал все фрагменты Base64, мне нужно было объединить их, декодировать и получить простой JSON.

Клиент может легко проверить, являются ли полученные данные ключом и действительны, потому что он может принимать данные, хешировать и сравнивать результат. Более того, клиент может рекурсивно проверять подлинность всех родительских узлов. Это то, что делает процедура проверки. Он представлен следующим фрагментом кода:

Как видно из предыдущего фрагмента, запись проверяется, а затем вся иерархия загружается и проверяется на согласованность данных.

Теперь, когда мы поняли, как записывать и читать данные из DNS, следующий шаг - как обеспечить их безопасность.

Криптография и ключи

Наша система может читать и записывать данные в DNS, и теперь пришло время позаботиться о безопасности. Мы предполагаем, что человеку, который пишет в наш DNS, доверяют, но мы не можем гарантировать, что вредоносный DNS-сервер не предоставит нам поддельные данные или что кто-то не прочитает их (помните, данные DNS являются общедоступными).

Я сделал здесь следующее улучшение протокола: хранимые данные хранятся в зашифрованном виде с использованием асимметричного алгоритма. Это гарантирует, что только производитель данных может генерировать данные, понятные для потребителей. Кто угодно может создать поддельный DNS-сервер, но он не сможет обработать вас поддельными данными. Более того, данные теперь зашифрованы, и никто не может их прочитать.

Асимметричный алгоритм идеален, потому что он позволяет только определенному количеству читателей понять сообщение, но только источник может их создать. Для этого клиент генерирует пару ключей. Открытый ключ используется для шифрования данных, поэтому производитель хранит их в безопасности. Закрытый ключ, который используется для дешифрования, передается потребителям. Им можно поделиться вручную, например, отправить по электронной почте в зашифрованном архиве или опубликовать на веб-сайте HTTPS, где сертификат может продемонстрировать пользователю полномочия.

Между прочим, концепция довольно проста: теперь данные зашифрованы, и никто не может писать в них от нашего имени. Но есть еще одна проблема. Симметричные алгоритмы работают с небольшим объемом данных (1024–4096 байт), но нам приходится обрабатывать огромную полезную нагрузку JSON. У нас есть два пути:

  • Разделите все сообщение на небольшие байтовые куски и зашифруйте \ расшифруйте их один за другим.
  • Создайте симметричный ключ, зашифруйте данные с помощью сгенерированного ключа, а затем зашифруйте сгенерированный ключ с помощью асимметричной пары. Таким образом, каждая запись имеет свой симметричный ключ, используемый для шифрования данных. Этот ключ является общедоступным, но может использоваться только теми, у кого есть закрытый ключ.

Учитывая вычислительные затраты на кодирование всех блоков байтов, я использовал второе решение. Это подводит нас к следующей полезной нагрузке:

{
   "data":"json object encrypted with the symm key",
   "key":"symm key encripted with the aymm alghorithm"
}

В приведенном выше фрагменте мы видим зашифрованные данные и зашифрованный ключ дешифрования, хранящиеся в полезной нагрузке JSON. Читатель будет использовать закрытый ключ для расшифровки симметричного ключа, а затем будет использовать его для расшифровки данных.

Изменения в коде минимальны: нужен лишь дополнительный шаг для упаковки \ распаковки данных.

В следующем фрагменте я показываю шаги, предпринятые для получения данных:

#generate a one time password
var password = SHA512.Create().ComputeHash(Guid.NewGuid().ToByteArray());
#encrypt the password
var decriptkey = this.cryptoService.EncodeKey(password, publicKey);
#encrypt data with the password
 var dataEnrypted = this.cryptoService.EncryptData(dataToEncode, password);
#json object is stored with decriptkey and dataEnrypted

В следующем фрагменте у нас есть процедура чтения:

var decriptKeyEncoded = .. from json
var dataEncrypted = ... from json   
var decriptKey = this.cryptoService.DecodeKey(decriptKeyEncoded, privateKey); 

var decodedData = this.cryptoService.DecodeData(dataEncrypted, decriptKey);
#decodedData is the plain data.

Теперь, когда мы закончили объяснение реализации блокчейна, у нас есть все детали для хранения данных в блокчейне DNS!

Выводы

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

Если бы нам приходилось обрабатывать безопасные неизменяемые данные, решением было бы использовать стандартную платформу блокчейна - под этим я имею в виду настоящую систему блокчейна.

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

Я уверен, что теперь у вас будет достаточно любопытства, чтобы попытаться включить блокчейн-решение в свой следующий проект.

Вы можете найти исходный код и полные спецификации протокола на GitHub.