Как работи децентрализираният мессенджър на блокчейн

В началото на 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, която е повредила не малко здраве.
  • Възможността да получите всичките си разговори от всяко устройство по всяко време - това е възможността да не съхранявате разговори локално изобщо.
  • Потвърждение на доставката на съобщения. Не на устройството на потребителя, а в мрежата. В същността си, това е потвърждение за възможността на получателя да прочете вашето съобщение. Това е полезна функция за изпращане на критични известия.

От предимствата на блокчейна също така е близката интеграция с криптовалути като Ethereum, Dogecoin, Lisk, Dash, Bitcoin (този все още е в процес) и възможността за изпращане на токени в чатове. Дори създадохме вграден крипто обменник.

А след това - как всичко това работи.

Съобщението е транзакция

Все вече са свикнали, че транзакциите в блокчейна предават токени (монети) от един потребител на друг. Как при биткоин. Ние създадохме специален тип транзакции за предаване на съобщения.

За да изпратите съобщение в месенджера на блокчейна, трябва да преминете през няколко етапа:

  1. Да шифровате текста на съобщението
  2. Да поставите шифрования текст в транзакцията
  3. Да подпишете транзакцията
  4. Да изпратите транзакцията на който и да е възел в мрежата
  5. Разпределената система от възли определя “достоверността” на съобщението
  6. Ако всичко е наред — транзакцията с съобщението се включва в следващия блок
  7. Получателят извлича транзакцията с съобщението и я дешифрира

Етапи 1–3 и 7 се изпълняват локално на клиента, а 5–6 — на възлите в мрежата.

Шифриране на съобщението

Съобщението се шифрова с частния ключ на изпращача и публичния ключ на получателя. Публичният ключ ще вземем от мрежата, но за това акаунтът на получателя трябва да бъде инициализиран, тоест да има поне една транзакция. Може да се използва REST-заявка GET /api/accounts/getPublicKey?address={ADAMANT address}, а при зареждане на чатовете публичните ключове на събеседниците вече ще бъдат налични.

Как работи децентрализираният мессенджър на блокчейн

Месенджърът шифрова съобщенията с алгоритъма curve25519xsalsa20poly1305 (NaCl Box). Тъй като акаунтът съдържа ключове Ed25519, за да се образува box’а, предварително ключовете трябва да се преобразуват в Curve25519 Diffie-Hellman.

Ето пример на JavaScript:

/**
 * 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 — nonce
  • тип — тип съобщение

Съобщенията също се делят на типове. Всъщност, параметърът тип казва как да разбираме 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, а след това се подписва. Ed25519 EdDSA и получаваме подпис подпис, а идентификаторът на транзакцията е част от SHA-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 — Изчисляваме SHA-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')
}

/**
 * Създава подпис на базата на хеш и ключова двойка.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {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 — validator.js и verify.js. Аха, възелът работи на Node.js.

Включваме транзакцията с съобщение в блока

Ако е постигнат консенсус, транзакцията с нашето съобщение ще попадне в следващия блок заедно с другите удостоверени транзакции.

Блоковете имат строга последователност и всеки следващ блок се формира на базата на хешовете на предишните блокове.

Как работи децентрализираният мессенджър на блокчейн

Същността е, че нашето съобщение също е включено в тази последователност и не може да бъде "преместено". Ако в блока попаднат няколко съобщения, редът им ще бъде определен по 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 се шифрова не NaCl-box, а NaCl-secretbox. Така съобщителят съхранява и други данни.

Предаването на файлове/изображения и груповите чатове изискват още много работа. Разбира се, в формат на прибързано решение това може да се "прикрепи" бързо, но искаме да запазим същото ниво на поверителност.

Да, има още много работа — в идеалния случай истинската поверителност предполага, че потребителите няма да се свързват с публични узли в мрежата, а ще създадат свои. Как мислите, колко процента от потребителите го правят? Правилно, 0. Частично успяхме да решим този въпрос с Tor-версията на съобщителя.

Доказахме, че съобщителят на блокчейн може да съществува. Преди това имаше само един опит през 2012 година — bitmessage, неуспешен поради голямото време за доставка на съобщения, натоварването на процесора и отсъствието на мобилни приложения.

Скептицизм се свързва с факта, че месенджърите на блокчейн изпреварват времето — хората все още не са готови да поемат отговорност за своите акаунти, притежанието на лична информация все още не е на мода, а технологиите не позволяват високи скорости на блокчейн. След нас ще се появят по-технологични аналози на нашия проект. Ще видите.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster