Како децентрализовани месинџер функционише на блокчејну?

В начале 2017 мы начали создавать мессенджер на блокчейне [название и ссылка есть в профиле] с обсуждения преимуществ перед классическими P2P-мессенджерами.

Отишла 2.5 года, и нам удалось подтвердить свой концепт: сейчас доступны приложения мессенджера для iOS, Web PWA, Windows, GNU/Linux, Mac OS и Android.

Сегодня мы расскажем, как устроен мессенджер на блокчейне и как клиентским приложениям работать с его API.
Како децентрализовани месинџер функционише на блокчејну?

Мы хотели, чтобы блокчейн решил вопросы безопасности и приватности классических P2P-мессенджеров:

  • Один клик для создания аккаунта — никаких телефонов и электронных почт, нет доступа к адресным книгам и геолокациям.
  • Собеседники никогда не устанавливают прямых соединений, все общение идет через распределенную систему узлов. IP-адресы пользователей недоступны друг другу.
  • Все сообщения шифруются End-to-End curve25519xsalsa20poly1305. Вроде бы этим никого не удивишь, но у нас-то исходный код открыт.
  • MITM-атака исключена — каждое сообщение является транзакцией и подписывается Ed25519 EdDSA.
  • Порука завршава у свом блоку. Доследност и timestamp блоков не исправишь, а следовательно и порядок сообщений.
  • “Я этого не говорил” не прокатит с сообщениями в блокчейне.
  • Не постоји централна структура која проверава „аутентичност“ поруке. То ради дистрибуирани систем чворова заснован на консензусу, а у власништву је корисника.
  • Невозможность цензуры — аккаунты нельзя блокировать, а сообщения удалять.
  • Блокчейн 2FA — альтернатива адской 2FA по SMS, упропастио много здравља.
  • Возможность получить все свои диалоги с любого устройства в любое время — это возможность не хранить диалоги локально вообще.
  • Подтверждение доставки сообщений. Не на устройство пользователя, а в сеть. По сути, это подтверждение возможности получателя прочитать ваше сообщение. Это полезная фича для отправки критических уведомлений.

Предности блокчејна такође укључују блиску интеграцију са криптовалутама Етхереум, Догецоин, Лиск, Дасх, Битцоин (ова је још увек у току) и могућност слања токена у четовима. Чак смо направили и уграђени крипто измењивач.

А онда – како то све функционише.

Порука је трансакција

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

Чтобы отправить сообщение в мессенджере на блокчейне, нужно пройти несколько этапов:

  1. Шифрујте текст поруке
  2. Ставите шифровани текст у трансакцију
  3. Потпишите трансакцију
  4. Отправить транзакцию на любой узел сети
  5. Распределенная система узлов определяет “достоверность” сообщения
  6. Если все ОК — транзакция с сообщением включается в следующий блок
  7. Получатель извлекает транзакцию с сообщением и расшифровывает

Этапы 1–3 и 7 выполняются локально на клиенте, а 5–6 — на узлах сети.

Шифровање поруке

Порука је шифрована приватним кључем пошиљаоца и јавним кључем примаоца. Узећемо јавни кључ са мреже, али за то налог примаоца мора бити иницијализован, односно имати најмање једну трансакцију. Можете користити РЕСТ захтев GET /api/accounts/getPublicKey?address={ADAMANT address}, а при загрузке чатов публичные ключи собеседников уже будут в наличии.

Како децентрализовани месинџер функционише на блокчејну?

Мессенджер шифрует сообщения алгоритмом curve25519xsalsa20poly1305 (НаЦл Бок). Поскольку аккаунт содержит ключи Ed25519, для формирования box’а предварительно ключи нужно преобразовать в Curve25519 Diffie-Hellman.

Ево примера у ЈаваСцрипт-у:

/**
 * Encodes a text message for sending to ADM
 * @param {string} msg message to encode
 * @param {*} recipientPublicKey recipient's public key
 * @param {*} privateKey our private key
 * @returns {{message: string, nonce: string}}
 */
adamant.encodeMessage = function (msg, recipientPublicKey, privateKey) {
  const nonce = Buffer.allocUnsafe(24)
  sodium.randombytes(nonce)

  if (typeof recipientPublicKey === 'string') {
    recipientPublicKey = hexToBytes(recipientPublicKey)
  }

  const plainText = Buffer.from(msg)
  const DHPublicKey = ed2curve.convertPublicKey(recipientPublicKey)
  const DHSecretKey = ed2curve.convertSecretKey(privateKey)

  const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey)

  return {
    message: bytesToHex(encrypted),
    nonce: bytesToHex(nonce)
  }
}

Формирање трансакције са поруком

Трансакција има следећу општу структуру:

{
  "id": "15161295239237781653",
  "height": 7585271,
  "blockId": "16391508373936326027",
  "type": 8,
  "block_timestamp": 45182260,
  "timestamp": 45182254,
  "senderPublicKey": "bd39cc708499ae91b937083463fce5e0668c2b37e78df28f69d132fce51d49ed",
  "senderId": "U16023712506749300952",
  "recipientId": "U17653312780572073341",
  "recipientPublicKey": "23d27f616e304ef2046a60b762683b8dabebe0d8fc26e5ecdb1d5f3d291dbe21",
  "amount": 204921300000000,
  "fee": 50000000,
  "signature": "3c8e551f60fedb81e52835c69e8b158eb1b8b3c89a04d3df5adc0d99017ffbcb06a7b16ad76d519f80df019c930960317a67e8d18ab1e85e575c9470000cf607",
  "signatures": [],
  "confirmations": 3660548,
  "asset": {}
}

За трансакцију поруке најважније је asset - потребно је да поставите поруку у објекат chat са структуром:

  • message - сачувајте шифровану поруку
  • own_message - једнократно
  • type — тип поруке

Сообщения тоже делятся на типы. По сути, параметр type говори вам како да разумете message. Можно отправить просто текст, а можно объект с интересностями внутри — например, так мессенджер делает переводы криптовалют в чатах.

Као резултат, креирамо трансакцију:

{
  "transaction": {
    "type": 8,
    "amount": 0,
    "senderId": "U12499126640447739963",
    "senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
    "asset": {
      "chat": {
        "message": "cb682accceef92d7cddaaddb787d1184ab5428",
        "own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
        "type": 1
      }
    },
    "recipientId": "U15677078342684640219",
    "timestamp": 63228087,
    "signature": "тут будет подпись"
  }
}

Потпис трансакције

Да би сви били сигурни у аутентичност пошиљаоца и примаоца, време слања и садржај поруке, трансакција се потписује. Дигитални потпис вам омогућава да проверите аутентичност трансакције помоћу јавног кључа - приватни кључ није потребан за ово.

А вот сама подпись как раз выполняется приватным ключом:

Како децентрализовани месинџер функционише на блокчејну?

Из схемы видно, что транзакцию сначала хешируем SHA-256, а потом подписываем Ед25519 ЕдДСА и добити потпис signature, а ИД трансакције је део СХА-256 хеша.

Пример имплементације:

1 — Формируем блок данных, включая сообщение

/**
 * Calls `getBytes` based on transaction type
 * @see privateTypes
 * @implements {ByteBuffer}
 * @param {transaction} trs
 * @param {boolean} skipSignature
 * @param {boolean} skipSecondSignature
 * @return {!Array} Contents as an ArrayBuffer.
 * @throws {error} If buffer fails.
 */

adamant.getBytes = function (transaction) {

  ...

  switch (transaction.type) {
    case constants.Transactions.SEND:
      break
    case constants.Transactions.CHAT_MESSAGE:
      assetBytes = this.chatGetBytes(transaction)
      assetSize = assetBytes.length
      break

…

    default:
      alert('Not supported yet')
  }

  var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true)

  bb.writeByte(transaction.type)
  bb.writeInt(transaction.timestamp)

  ...

  bb.flip()
  var arrayBuffer = new Uint8Array(bb.toArrayBuffer())
  var buffer = []

  for (var i = 0; i < arrayBuffer.length; i++) {
    buffer[i] = arrayBuffer[i]
  }

  return Buffer.from(buffer)
}

2 - Бројите СХА-256 из блока података

/**
 * Creates hash based on transaction bytes.
 * @implements {getBytes}
 * @implements {crypto.createHash}
 * @param {transaction} trs
 * @return {hash} sha256 crypto hash
 */
adamant.getHash = function (trs) {
  return crypto.createHash('sha256').update(this.getBytes(trs)).digest()
}

3 — Потпишите трансакцију

adamant.transactionSign = function (trs, keypair) {
  var hash = this.getHash(trs)
  return this.sign(hash, keypair).toString('hex')
}

/**
 * Creates a signature based on a hash and a keypair.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} signature
 */
adamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Слање трансакције са поруком мрежном чвору

Поскольку сеть децентрализованная, подойдет любой из узлов с открытым API. Делаем POST-запрос на эндпоинт api/transactions:

curl 'api/transactions' -X POST 
  -d 'TX_DATA'

В ответ получим ID транзакции типа

{
    "success": true,
    "nodeTimestamp": 63228852,
    "transactionId": "6146865104403680934"
}

Валидација трансакције

Дистрибуирани систем чворова, заснован на консензусу, одређује „аутентичност“ поруке о трансакцији. Од кога и коме, када, да ли је порука замењена другом и да ли је тачно назначено време слања. Ово је веома важна предност блокчејна – не постоји централна структура која је одговорна за верификацију, а редослед порука и њихов садржај се не могу лажирати.

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

Како децентрализовани месинџер функционише на блокчејну?

Часть кода узла, которая отвечает за проверки, можно посмотреть в GitHub — валидатор.јс и верифи.јс. Да, чвор ради на Ноде.јс.

Укључујући трансакцију са поруком у блоку

Если консенсус достигнут, транзакция с нашим сообщением попадет в следующий блок наряду с другими достоверными транзакциями.

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

Како децентрализовани месинџер функционише на блокчејну?

Суть в том, что наше сообщение также включено в эту последовательность и не может быть “переставлено”. Если в блок попадает несколько сообщений, их порядок будет определен по timestamp поруке.

Читање порука

Приложение-мессенджер извлекает транзакции из блокчейна, которые отправлены адресату. Для этого мы сделали эндпоинт api/chatrooms.

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

**
 * Decodes the incoming message
 * @param {any} msg encoded message
 * @param {string} senderPublicKey sender public key
 * @param {string} privateKey our private key
 * @param {any} nonce nonce
 * @returns {string}
 */
adamant.decodeMessage = function (msg, senderPublicKey, privateKey, nonce) {
  if (typeof msg === 'string') {
    msg = hexToBytes(msg)
  }

  if (typeof nonce === 'string') {
    nonce = hexToBytes(nonce)
  }

  if (typeof senderPublicKey === 'string') {
    senderPublicKey = hexToBytes(senderPublicKey)
  }

  if (typeof privateKey === 'string') {
    privateKey = hexToBytes(privateKey)
  }

  const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey)
  const DHSecretKey = ed2curve.convertSecretKey(privateKey)
  const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey)

  return decrypted ? decode(decrypted) : ''
}

И шта још?

Пошто се поруке испоручују на овај начин за око 5 секунди – ово је време када се појављује нови мрежни блок – дошли смо до везе клијент-чвор и чвор-чвор сокета. Када чвор прими нову трансакцију, проверава њену валидност и прослеђује је другим чворовима. Трансакција је доступна клијентима месинџера чак и пре него што дође до консензуса и укључивања у блок. На овај начин ћемо испоручити поруке тренутно, баш као и обични инстант мессенгери.

Чтобы хранить адресную книгу, мы сделали KVS — Key-Value Storage — это еще один тип транзакций, в которых asset није НаЦл-кутија та која је шифрована, већ НаЦл-тајна кутија. Овако месинџер чува остале податке.

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

Да, има још посла – у идеалном случају, права приватност претпоставља да се корисници неће повезивати на јавне мрежне чворове, већ ће подићи своје. Шта мислите, колики проценат корисника то ради? Тако је, 0. Успели смо делимично да решимо овај проблем са Тор верзијом месинџера.

Мы доказали, что мессенджер на блокчейне может существовать. Ранее была только одна попытка в 2012 году — битмессаге, неудавшаяся из-за большого времени доставки сообщений, нагрузки на процессор и отсутствия мобильных приложений.

А скептицизам је због чињенице да су гласници на блокчејну испред свог времена – људи нису спремни да преузму одговорност за свој налог, поседовање личних података још није тренд, а технологија не дозвољава велике брзине на блокчејну. Следеће ће се појавити више технолошких аналога нашег пројекта. Видећете.

Извор: ввв.хабр.цом

Додај коментар