Децентрализация идентичности

Александр Котов децентрализация распределённые приложения bitcoin dash криптовалюты Medium

Создание полностью распределённых сервисов – это очень сложная задача. О причинах этого мы уже говорили в статьях “Децентрализованные сервисы против распределённых” и “Перспективы избавления от цензуры технологических корпораций”. В то же время федеративные сервисы вынуждают выбирать либо крупные сервера, что способствует централизации с неизбежно следующими за ней цензурой и слежкой, либо маленькие сервера, которые имеют высокие издержки на их содержание и не могут гарантировать долговечность. В обоих случаях ваш идентификатор и ваши данные оказываются навсегда привязанными к выбранному серверу.

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

Подходы

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

Привязка к личности

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

  • Человека часто нельзя однозначно идентифицировать по комбинации фамилии, имени, отчества и даты рождения.
  • Человека можно однозначно идентифицировать по паспортным данным, но они обычно не должны быть публичными.
  • Во многих странах, не говоря уже о международном сообществе, отсутствует какой-либо универсальный публичный идентификатор личности.
  • Органы государственной власти не всегда работают быстро и порой для получения услуг требуется оплата пошлины.
  • Люди небезосновательно не хотят предоставлять свои личные данные компаниям и другим людям.
  • Такой подход ещё больше увеличивает зависимость человека от государства, что может быть даже хуже, чем зависимость от корпораций.

Хотя с ростом цифровизации многие государства будут стремиться решать технические проблемы и уменьшать издержки взаимодействия с органами власти через Интернет (например, больших успехов в этом достигли в Эстонии), стоит избегать такого взаимодействия кроме как в тех сферах, где у государства уже есть монополия.

Номер телефона

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

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

Также этот способ не обеспечивает настоящей децентрализации. Например, в федеративном протоколе обмена сообщениями Matrix возможна идентификация по номеру телефона, но она требует привязки к конкретному сервису, называемому identity server.

Адрес электронной почты

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

Централизованный сервер идентификации

Можно делегировать идентификатор некоторому стороннему сервису, который будет аутентифицировать пользователей, например, по протоколам OAuth или OpenID. Такую возможность предоставляют многие крупные сервисы, такие как Google, Facebook, VK, GitHub. Этот способ не решает проблему централизации, но по крайней мере позволяет вам выбрать того поставщика услуги, которого вы считаете наиболее надёжным.

Публичный криптографический ключ

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

Сам по себе публичный ключ ничего не говорит о том, как найти его владельца, поэтому этот способ требует дополнительной инфраструктуры. Например, в протоколе обмена электронными сообщениями Tox используется одноранговая сеть с распределённой хэш-таблицей (англ. DHT – distributed hash table).

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

Криптовалютный адрес

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

Большинство криптовалют позволяют привязать к адресу произвольные данные ограниченной длины. Не только Ethereum, который в том числе для этого и создавался, но и предназначенные исключительно для хранения финансовой информации криптовалюты, такие как Bitcoin или Dash, за счёт команды OP_RETURN. Именно практическое использование OP_RETURN мы рассмотрим позже в этой статье.

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

Decentralized Identifiers (DIDs)

Сейчас World Wide Web Consortium разрабатывает стандарт “Decentralized Identifiers (DIDs)”, который должен обобщить все возможные подходы к идентификации. По этому стандарту идентификатор содержит в себе метод и собственно идентификатор, специфичный для данного метода.

Идентификатор по стандарту DID.
Идентификатор по стандарту DID.

Стандарт описывает больше, чем просто идентификаторы. Эта строка позволяет получить целый документ в формате JSON, который уже описывает, как найти его владельца, содержит криптографические публичные ключи и другие идентфикаторы. Это целая инфраструктура, которая даёт возможность в том числе для безопасной миграции с одного метода на другой.

Элементы стандарта DID.
Элементы стандарта DID.

Допустим, изначально вы представились по номеру телефона и получили такой идентификатор: did:phone:1–669–643–6395 (он не соответствует никакому стандарту и используется в качестве примера). Затем вы решили, что желаете использовать адрес Bitcoin вместо номера телефона и получаете такой идентификатор: did:bitcoin:35nA4yNtWUMGuVZCa4y49NwRDmsb6t4VQy. При этом в документе, соответствующем предыдущему идентификатору, вы указываете ваш новый идентификатор. Теоретически это даёт возможность уведомить всех об изменении. Как это будет реализовано на практике пока неизвестно, однако стандарт выглядит весьма перспективным.

Пример идентификации с помощью криптовалютного адреса

Как и было обещано ранее, рассмотрим пример идентификации с помощью криптовалютного адреса и команды OP_RETURN. Будем использовать криптовалюту Dash, поскольку её блокчейн занимает мало места (27 ГБ на момент написания статьи), быстро загружается (у меня это заняло менее двух часов), а транзакции стоят очень дёшево (меньше одного цента). В Bitcoin, от которого происходит Dash, это работает аналогичным образом (за исключением нововведений, появившихся в результате обновления BIP 141 под названием “SegWit”).

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

Теория

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

Дело в том, что транзакция в Bitcoin (и в Dash) устроена достаточно сложно. Это не просто запись о том, откуда, куда и какое количество средств нужно передать. Транзакция состоит из нескольких входов и выходов. Вход каждой следующей транзакции указывает на выход какой-то предыдущей транзакции. С каждым выходом и, соответственно, входом связана денежная сумма. Сеть проверяет, чтобы сумма входов была больше суммы выходов, разница является комиссией сети. Для рядового пользователя кошелька эта сложность не видна, потому что кошелёк автоматически подбирает выходы для использования их в качестве входов новой транзакции.

Устройство транзакции Bitcoin (и Dash).
Устройство транзакции Bitcoin (и Dash).

Естественно, потратить выход любой транзакции может не каждый, а только тот, кто владеет приватным криптографическим ключом, соответствующий публичный ключ которого зафиксирован в этом выходе. Так обеспечивается передача денежных средств между пользователями. Задаются эти условия на специальном языке программирования Script. Это стековый язык программирования, похожий на Forth. В нём данные и код перемешаны.

Возможность указать данные вместе с кодом использовалась теми, кто хотел записать какую-то произвольную информацию в блокчейн. Это засоряло глобальный индекс непотраченных выходов транзакций (англ. UTXO – unspent transaction output), а запретить такие “ненастоящие” выходы оказалось невозможно. Чтобы исправить эту проблему и была добавлена команда языка Script OP_RETURN. Она позволила добавлять в выход транзакции небольшой объём данных – сначала 40 байт, сейчас 83 байта. При этом такой выход помечается как “гарантированно непотрачиваемый” и не попадает в индекс UTXO.

Для любого адреса можно получить список таких выходов транзакций в хронологическом порядке, что позволяет соотнести с этим адресом любые данные. Мы будем соотносить с ним строку в формате user@example.com, где user – это имя пользователя, а example.com – один из серверов воображаемой федеративной социальной сети, в которой мы хотим искать пользователя по этому адресу. В наших примерах будет использоваться адрес XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G, данные по которому можно увидеть в обозревателе блоков. Если вы решите повторить инструкции, ваш адрес будет отличаться.

Практика: настройка узла

Вам потребуется операционная система на основе GNU/Linux с командами curl, wget, gpg и ruby, навыки работы в командной строке, полный узел Dash и немного денег на оплату комиссии сети.

Загрузим программное обеспечение узла Dash и его электронную цифровую подпись:

$ wget https://github.com/dashpay/dash/releases/download/v0.16.1.1/dashcore-0.16.1.1-x86_64-linux-gnu.tar.gz
$ wget https://github.com/dashpay/dash/releases/download/v0.16.1.1/dashcore-0.16.1.1-x86_64-linux-gnu.tar.gz.asc

Загрузим и импортируем ключ, которым подписано программное обеспечение, и проверим, что никто не подменил файл:

$ curl 'https://keybase.io/pasta/pgp_keys.asc?fingerprint=29590362ec878a81fd3c202b52527bedabe87984' | gpg --import -
$ gpg --verify dashcore-0.16.1.1-x86_64-linux-gnu.tar.gz.asc

На экране должно появиться сообщение, содержащее строку Good signature from "Pasta <pasta@dashboost.org>".

Распакуем загруженный архив и перейдём в директорию с исполняемыми файлами:

$ tar -xvf dashcore-0.16.1.1-x86_64-linux-gnu.tar.gz
$ cd dashcore-0.16.1/bin/

Создадим конфигурационный файл узла Dash и запустим узел. Стоит отметить, что данная конфигурация не является безопасной. Не используйте её в реальных проектах. Здесь она используется лишь для того, чтобы упростить пример:

$ mkdir ~/.dashcore
$ echo 'printtoconsole=1'     >>~/.dashcore/dash.conf
$ echo 'rpcpassword=password' >>~/.dashcore/dash.conf
$ echo 'rpcuser=user'         >>~/.dashcore/dash.conf
$ echo 'txindex=1'            >>~/.dashcore/dash.conf
$ ./dashd

Загрузка блокчейна займёт меньше двух часов при хорошем соединении с Интернетом и быстром диске. При этом на экране вы будете видеть сообщения следующего вида:

UpdateTip: new best=000000000000001597d3157332b219f0885e0851c4995ac30b67e538eec62cae height=1434048 version=0x20000000 log2_work=78.35472297 tx=33899715 date='2021-03-08 13:02:09' progress=0.999997 cache=0.9MiB(6480txo) evodb_cache=0.0MiB

Обратите внимание на параметр progress. Достижение им значения 0.9999 означает, что блокчейн полностью загрузился. После этого можно продолжить выполнение команд в другой вкладке терминала, не прерывая запущенный процесс dashd.

Практика: запись данных

Для начала получите адрес вашего кошелька:

$ ./dash-cli getnewaddress
XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G

Отправьте на этот адрес деньги и проверьте, что они дошли:

$ ./dash-cli getbalance
0.05942754

Теперь нужно сформировать транзакцию, в которой мы привяжем к адресу строку user@example.com. Сеть не примет транзакцию, состоящую только из одного выхода типа OP_RETURN, поэтому мы также добавим в транзакцию отправку денег обратно на этот же адрес. Это позволит нам в дальнейшем создавать новые выходы типа OP_RETURN с этого же адреса.

Строка должна быть представлена в шестнадцатеричном виде. Конвертировать её можно с помощью онлайн-сервисов, например https://www.asciitohex.com, или с помощью языка программирования Ruby:

$ irb
irb(main):001:0> 'user@example.com'.unpack1 'H*'
=> "75736572406578616d706c652e636f6d"

Для формирования транзакции используем полученную шестнадцатеричную строку и тот же адрес, на который мы получим деньги обратно. Обратите внимание, что сумму нужно указывать чуть меньше имеющейся, чтобы хватило ещё на комиссию сети. На момент написания статьи комиссии составляют меньше 0.00001000 DASH:

$ ./dash-cli createrawtransaction '[]' '{"data":"75736572406578616d706c652e636f6d","XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G":0.05941754}'
0200000000020000000000000000126a1075736572406578616d706c652e636f6dfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000

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

$ ./dash-cli fundrawtransaction '0200000000020000000000000000126a1075736572406578616d706c652e636f6dfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000'
{
  "hex": "0200000001064ee23625fa8b775a2c30cc649fe9b25b8d09ecfc31c4f0e4fb6f3d72d167cf0000000000feffffff030000000000000000126a1075736572406578616d706c652e636f6deb020000000000001976a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788acfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000",
  "changepos": 1,
  "fee": 0.00000253
}

Комиссия (поле fee) оказалась меньше той, на которую мы рассчитывали. Остаток уйдёт на другой адрес нашего же кошелька.

Теперь транзакцию нужно подписать. Берём шестнадцатеричную строку из поля hex и передаём следующей команде:

$ ./dash-cli signrawtransaction '0200000001064ee23625fa8b775a2c30cc649fe9b25b8d09ecfc31c4f0e4fb6f3d72d167cf0000000000feffffff030000000000000000126a1075736572406578616d706c652e636f6deb020000000000001976a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788acfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000'
{
  "hex": "0200000001064ee23625fa8b775a2c30cc649fe9b25b8d09ecfc31c4f0e4fb6f3d72d167cf000000006a47304402206a56172e70955140fc539b193118183b79498105e321436243178d5a41752fc902202354b30522d7dc4c92168702baa5d406a2f8a00b2f0586dbfec50a886f28301d012103e4caed1991b48a6e03869bfba95afcdcfadfd0b69275f2ce4f28713833c57c25feffffff030000000000000000126a1075736572406578616d706c652e636f6deb020000000000001976a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788acfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000",
  "complete": true
}

Осталось передать транзакцию в сеть. Снова берём шестнадцатеричную строку из поля hex и передаём следующей команде:

$ ./dash-cli sendrawtransaction '0200000001064ee23625fa8b775a2c30cc649fe9b25b8d09ecfc31c4f0e4fb6f3d72d167cf000000006a47304402206a56172e70955140fc539b193118183b79498105e321436243178d5a41752fc902202354b30522d7dc4c92168702baa5d406a2f8a00b2f0586dbfec50a886f28301d012103e4caed1991b48a6e03869bfba95afcdcfadfd0b69275f2ce4f28713833c57c25feffffff030000000000000000126a1075736572406578616d706c652e636f6deb020000000000001976a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788acfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000'
d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573

Эта транзакция (d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573) появилась в обозревателе блоков по нашему адресу. В поле Recipients мы увидим строку, начинующуюся на OP_RETURN. При наведении на неё курсора всплывает расшифровка: OP_RETURN: j user@example.com. Как мы видим, это именно та строка, которую мы хотели связать с нашим адресом. После подтверждения транзации она навсегда останется в блокчейне.

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

Практика: чтение данных

Простейшей политикой выбора актуальной строки для адреса может быть выбор последней транзакции с выходом типа OP_RETURN, имеющей не меньше определённого количества подтверждений. Мы не будем рассматривать вопросы безопасности, отслеживание чужих адресов (для этого обратите внимание на команду Bitcoin и Dash importaddress) и сложные ситуации, например, когда в одной транзакции несколько выходов типа OP_RETURN (такое должно быть возможно, но я не пробовал воспроизвести). Естественно, для реального применения дальнейший код требует доработки и тестирования. Сейчас мы реализуем Proof of Concept.

Для начала получим список транзакций кошелька:

$ ./dash-cli listtransactions "*" 100 0
[
  ...
  {
    "account": "",
    "address": "XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G",
    "category": "receive",
    "amount": 0.05941754,
    "label": "",
    "vout": 2,
    "confirmations": 8,
    "instantlock": true,
    "instantlock_internal": false,
    "chainlock": true,
    "blockhash": "000000000000000508e564ded02b11f0b748b60777696ee86c9590b28b796589",
    "blockindex": 136,
    "blocktime": 1615210860,
    "txid": "d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573",
    "walletconflicts": [
    ],
    "time": 1615210759,
    "timereceived": 1615210759
  },
  {
    "account": "",
    "address": "XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G",
    "category": "send",
    "amount": -0.05941754,
    "label": "",
    "vout": 2,
    "fee": -0.00000253,
    "confirmations": 8,
    "instantlock": true,
    "instantlock_internal": false,
    "chainlock": true,
    "blockhash": "000000000000000508e564ded02b11f0b748b60777696ee86c9590b28b796589",
    "blockindex": 136,
    "blocktime": 1615210860,
    "txid": "d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573",
    "walletconflicts": [
    ],
    "time": 1615210759,
    "timereceived": 1615210759,
    "abandoned": false
  },
  {
    "account": "",
    "category": "send",
    "amount": 0.00000000,
    "vout": 0,
    "fee": -0.00000253,
    "confirmations": 8,
    "instantlock": true,
    "instantlock_internal": false,
    "chainlock": true,
    "blockhash": "000000000000000508e564ded02b11f0b748b60777696ee86c9590b28b796589",
    "blockindex": 136,
    "blocktime": 1615210860,
    "txid": "d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573",
    "walletconflicts": [
    ],
    "time": 1615210759,
    "timereceived": 1615210759,
    "abandoned": false
  }
]

Мы видим в списке нашу транзакцию d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573 (в поле txid), причём в нескольких экземплярах, а также видим другие транзакции. В коде мы отфильтруем их, а пока посмотрим подробности о нашей транзакции:

$ ./dash-cli getrawtransaction 'd32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573' 1
{
  "txid": "d32faf65d87a282eac93851c728a7eadbaf034a6bec6e8619c01874696d0f573",
  "version": 2,
  "type": 0,
  "size": 252,
  "locktime": 0,
  "vin": [
    {
      "txid": "cf67d1723d6ffbe4f0c431fcec098d5bb2e99f64cc302c5a778bfa2536e24e06",
      "vout": 0,
      "scriptSig": {
        "asm": "304402206a56172e70955140fc539b193118183b79498105e321436243178d5a41752fc902202354b30522d7dc4c92168702baa5d406a2f8a00b2f0586dbfec50a886f28301d[ALL] 03e4caed1991b48a6e03869bfba95afcdcfadfd0b69275f2ce4f28713833c57c25",
        "hex": "47304402206a56172e70955140fc539b193118183b79498105e321436243178d5a41752fc902202354b30522d7dc4c92168702baa5d406a2f8a00b2f0586dbfec50a886f28301d012103e4caed1991b48a6e03869bfba95afcdcfadfd0b69275f2ce4f28713833c57c25"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.00000000,
      "valueSat": 0,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_RETURN 75736572406578616d706c652e636f6d",
        "hex": "6a1075736572406578616d706c652e636f6d",
        "type": "nulldata"
      }
    },
    {
      "value": 0.00000747,
      "valueSat": 747,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 52e671466a252dfa59b0f8428b31d0b6fc7b7ad7 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "XiFBJQc8tdx3YeTXRZMoqr38yrxaug1uWo"
        ]
      }
    },
    {
      "value": 0.05941754,
      "valueSat": 5941754,
      "n": 2,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 4af2f6862e42cdaf0bb162fda00bd8096e7af271 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G"
        ]
      }
    }
  ],
  "hex": "0200000001064ee23625fa8b775a2c30cc649fe9b25b8d09ecfc31c4f0e4fb6f3d72d167cf000000006a47304402206a56172e70955140fc539b193118183b79498105e321436243178d5a41752fc902202354b30522d7dc4c92168702baa5d406a2f8a00b2f0586dbfec50a886f28301d012103e4caed1991b48a6e03869bfba95afcdcfadfd0b69275f2ce4f28713833c57c25feffffff030000000000000000126a1075736572406578616d706c652e636f6deb020000000000001976a91452e671466a252dfa59b0f8428b31d0b6fc7b7ad788acfaa95a00000000001976a9144af2f6862e42cdaf0bb162fda00bd8096e7af27188ac00000000",
  "blockhash": "000000000000000508e564ded02b11f0b748b60777696ee86c9590b28b796589",
  "height": 1434062,
  "confirmations": 12,
  "time": 1615210860,
  "blocktime": 1615210860,
  "instantlock": true,
  "instantlock_internal": false,
  "chainlock": true
}

Из результатов первой команды нам потребуются поля address (наш адрес), category (значение send), confirmations (как минимум больше нуля), txid (отсюда возьмём идентификатор транзакции). Из результатов второй команды – вложенное поле vout[*].scriptPubKey.asm (должно начинаться на OP_RETURN, из остатка строки возьмём искомое значение).

Дальнейшая обработка невозможна без написания программного кода. Мы опять будем использовать язык программирования Ruby. Если вы его не знаете, то лучше прочитайте остаток статьи без выполнения инструкций, потому что выполнять код, который вы не понимаете, небезопасно.

Скопируйте этот код в файл op_return.rb:

require 'json'

# Настраиваемые параметры алгоритма.
ADDRESS = 'XhX8tp7Na4Dx4HVm3h7eq3Tpmy2pYtEu6G'
MIN_CONFIRMATIONS = 1


OP_RETURN = 'OP_RETURN '

all_tx_datas = JSON.parse %x[dash-cli listtransactions "*" 100]

tx_datas = all_tx_datas.select do |tx_data|
  tx_data['address'] == ADDRESS &&
    tx_data['category'] == 'send' &&
    tx_data['confirmations'] >= MIN_CONFIRMATIONS
end

tx_ids = tx_datas.map { |tx_data| tx_data['txid'] }.uniq

tx_full_datas = tx_ids.map do |tx_id|
  # Небезопасная подстановка значения без проверки!
  JSON.parse %x[dash-cli getrawtransaction #{tx_id} 1]
end

all_tx_vouts = tx_full_datas.flat_map do |tx_full_data|
  tx_full_data['vout'].map do |vout|
    tx_full_data.merge 'vout' => vout
  end
end

tx_vouts = all_tx_vouts.select do |tx_vout|
  tx_vout['vout']['scriptPubKey'] != nil &&
    tx_vout['vout']['scriptPubKey']['asm'] != nil &&
    tx_vout['vout']['scriptPubKey']['asm'].match?(/\A#{OP_RETURN}/)
end

actual_tx_vout = tx_vouts.sort { |tx_vout| tx_vout['confirmations'] }.first

asm = actual_tx_vout['vout']['scriptPubKey']['asm']
hex = asm[OP_RETURN.length..-1]
ascii = [hex].pack 'H*'

puts ascii

Запустите код:

$ ruby op_return.rb
user@example.com

Как видите, мы получили то самое значение, которое привязали к адресу.

Выводы

Несмотря на неразвитость стандартов и сложность использования конечными пользователями, разработчикам децентрализованных сервисов стоит попробовать внедрить альтернативные способы идентификации пользователей с помощью криптовалютных адресов. Только практика поможет усовершенствовать их. Использование распределённых идентификаторов устраняет значительную часть проблем федеративных сервисов.