Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

2017. aasta alguses alustasime [profil on nimi ja link] plokiahela põhjal messengeri arendamist, arutades selle eeliseid traditsiooniliste P2P messengeri ees.

On möödunud 2.5 aastat ja oleme suutnud oma kontseptsiooni kinnitada: nüüd on messengeri rakendused saadaval iOS, Web PWA, Windows, GNU/Linux, Mac OS ja Android jaoks.

Täna räägime sellest, kuidas plokiahela põhine messenger töötab ja kuidas kliendirakendused saavad selle API-ga suhelda.
Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

Soovisime, et plokiahel lahendaks traditsiooniliste P2P messengeri turvalisuse ja privaatsuse probleemid:

  • Konto loomiseks piisab ühest klikki — mingeid telefone ega e-kirju ei nõuta, puudub ligipääs aadressiraamatutele ja geolokatsioonidele.
  • Kaastöötajad ei loo kunagi otseseid ühendusi, kogu suhtlemine toimub hajutatud sõlmede süsteemi kaudu. Kasutajate IP-aadressid pole üksteisele kergelt kätetõstmiseks kätte saadavad.
  • Kõik sõnumid on šifreeritud End-to-End curve25519xsalsa20poly1305. See ei tohiks kedagi üllatada, kuid meil on algallikas avatud.
  • MITM-rünnakud on välistatud — iga sõnum on tehing ja allkirjastatud Ed25519 EdDSA-ga.
  • Sõnum siseneb oma plokki. Järjekord ja timestamp plokke ei saa muuta, seega ka sõnumite järjekord.
  • „Ma ei öelnud seda“ ei toimi plokiahelal põhinevate sõnumite puhul.
  • Pole tsentraliseeritud struktuuri, mis kontrolliks sõnumi „usaldusväärsust“. Seda teevad konsensusele põhinevad jaotatud sõlmed, mis kuuluvad kasutajatele.
  • Tsensuuri puudumine — kontosid ei saa blokeerida ja sõnumeid kustutada.
  • Plokiahela 2FA — alternatiiv õudsele SMS-2FA-le, mis on rikkunud palju elu.
  • Võimalus saada kõik oma vestlused igalt seadmelt igal ajal — see tähendab, et vestlusi ei pea üldse kohapeal hoidma.
  • Sõnumite kohaletoimetamise kinnitamine. Mitte kasutaja seadmesse, vaid võrku. Sisuliselt on see kinnitamine, et saaja suudab teie sõnumit lugeda. See on kasulik funktsioon kriitiliste teadete saatmiseks.

Plokiahela võimalustest on ka tihe integratsioon krüptorahadega nagu Ethereum, Dogecoin, Lisk, Dash, Bitcoin (see on veel töös) ning võimalus saata token'e vestlustes. Me oleme isegi loonud sisseehitatud krüpto vahetuskoha.

Edasi — kuidas kõik see töötab.

Sõnum on tehing.

Kõik on juba harjunud, et plokiahelas toimuvad tehingud edastavad tokenid (mündid) ühest kasutajast teisele. Nagu Bitcoinis. Oleme loonud erilise tüübi tehinguteks, et edastada sõnumeid.

Sõnumi saatmiseks plokiahela sõnumitoojasse tuleb läbida mitu etappi:

  1. Krüpteerida sõnumi tekst
  2. Pane krüpteeritud tekst tehingusse
  3. Allkirjastada tehing
  4. Saata tehing ühelegi võrgu sõlmele
  5. Jaotatud sõlmede süsteem määrab sõnumi "tõepärasuse"
  6. Kui kõik on korras — sisaldab sõnumitega tehing järgmises plokis
  7. Saaja ekstraktib sõnumiga tehingu ja dekrüpteerib selle.

Etapid 1–3 ja 7 toimuvad kohapeal kliendil, samas kui 5–6 toimub võrgu sõlmedes.

Sõnumi krüpteerimine

Sõnum krüpteeritakse saatja privaatvõtme ja saaja avaliku võtmega. Avaliku võtme võtame võrgust, kuid selleks peab saaja konto olema initsialiseeritud, st olema vähemalt üks tehing. Võib kasutada REST-päringut GET /api/accounts/getPublicKey?address={ADAMANT address}, ja vestluste laadimise ajal on vestluspartnerite avalikud võtmed juba saadaval.

Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

Sõnumite edastamisel kasutatakse curve25519xsalsa20poly1305 algoritmi (NaCl Box). Kuna kontol on Ed25519 võtmed, tuleb box'i loomiseks need eelnevalt Curve25519 Diffie-Hellman'i võtmeteks konvertida.

Siin on näide JavaScript'is:

/**
 * 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)
  }
}

Sõnumiga tehingu koostamine

Tehingul on järgmine üldine struktuur:

{
  "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": {}
}

Tehingu-sõnumi jaoks on kõige olulisem väärtus asset — sinna tuleb paigutada sõnum objekti chat struktuuriga:

  • message — salvestame krüpteeritud sõnumi
  • own_message — nonce
  • type — sõnumi tüüp

Sõnumid jagunevad samuti tüüpideks. Tegelikult näitab parameeter type kuidas aru saada message. Saate saata lihtsalt teksti või objekti huvitavate elementidega sees — näiteks, nii nagu sõnumitooja teeb krüptovaluuta ülekandeid vestlustes.

Lõpuks vormime tehingu:

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

Tehingu allkiri

Kuna kõik peavad olema kindlad saadetaja ja saaja, saatmise aja ja sõnumi sisu õigsuses, allkirjastatakse tehing. Digitaalne allkiri võimaldab kontrollida tehingu usaldusväärsust avaliku võtme abil — selleks ei ole vajalik privaatne võti.

Allkiri tehakse peamiselt privaatse võtmega:

Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

Diagramm näitab, et tehingu hashitakse esmalt SHA-256 ja seejärel allkirjastatakse. Ed25519 EdDSA ja saame allkirja signature, ning tehingu identifikaator on SHA-256 hash'i osa.

Näide rakendamisest:

1 — Vormime andmeploki, sealhulgas sõnumi

/**
 * 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 — Arvutame andmeploki 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 — Allkirjastame tehingu

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

/**
 * Loob kvaliteedi põhjal allkiri ja võtmeühik.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} allkiri
 */
adamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Tehingu saatmine sõnumiga võrgu sõlmele

Kuna võrk on detsentraliseeritud, sobib igaüks, kellel on avatud API. Teeme POST-päringu lõpp-punktile api/transactions:

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

Saame vastuseks tehingu ID tüüpi

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

Tehingu kehtivuse kontrollimine

Jagatud sõlmede süsteem, mis põhineb konsensusel, määrab tehingu-sõnumi "kehtivuse". Kes, kellele, millal, kas sõnum ei ole asendatud teisega, kas õige saatmise aeg on märgitud. See on väga oluline plokiahela eelis — puudub keskne struktuur, mis vastutab kontrollide eest, ning sõnumite järjestus ja sisu ei ole vale.

Esialgu kontrollib kehtivust üks sõlm ja seejärel saadab teistele — kui enamus ütleb, et kõik on korras, lisatakse tehing järgmisse plokki — see ongi konsensus.

Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

Node'i koodiosa, mis vastutab kontrollide eest, on saadaval GitHubis — validator.js ja verify.js. Jah, node töötab Node.js-il.

Lülitame tehingu sõnumi plokki

Kui konsensus on saavutatud, lisatakse meie sõnum järgmisse plokki koos teiste usaldusväärsete tehingutega.

Plokid järgivad ranged järjestust, kus iga järgmine plokk vormistatakse eelnevate plokkide hash'ide põhjal.

Kuidas töötab detsentraliseeritud sõnumivahetus plokiahelal

Oluline on see, et meie sõnum on samuti osa sellest järjestusest ja ei saa olla “ümber vahetatud”. Kui plokki satub mitu sõnumit, määratakse nende järjekord timestamp sõnumite järgi.

Sõnumite lugemine

Sõnumirakendus tõmbab plokiahelast tehingud, mis on saadetud adressaadile. Selle jaoks oleme loonud lõpp-punkti api/chatrooms.

Kõik tehingud on igasühe jaoks kergesti kättesaadavad — on võimalik saada krüpteeritud sõnumeid. Kuid dekrüpteerida suudab ainult saaja oma privaatse ning saatja avaliku võtmega:

**
 * 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) : ''
}

Aga mis veel?

Kuna sõnumid arriveeruvad sel viisil umbes 5 sekundit — see on aega, kui ilmub uus võrgu plokk — oleme välja töötanud socket-ühenduse klient-sõlm ja sõlm-sõlm. Kui sõlm saab uue tehingu, kontrollib ta selle kehtivust ja edastab selle teistele sõlmedele. Tehing on kliendimessengeritele kättesaadav juba enne konsensuse saavutamist ja plokki lisamist. Nii edastame sõnumid koheselt, nagu tavalised messengerid.

Aadressiraamatu salvestamiseks oleme loonud KVS — Key-Value Storage — see on veel üks tehingute tüüp, kus asset krüpteeritakse mitte NaCl-box, vaid NaCl-secretbox. Nii salvestab messenger ka muid andmeid.

Failide/piltide edastamine ja grupivestlused nõuavad veel palju tööd. Muidugi saab seda kiiresti ‘kinnitada’ kergelt, kuid me tahame säilitada sama privaatsuse taseme.

Jah, on veel millega tegeleda — ideaalis tähendaks tõeline privaatsus, et kasutajad ei oleks ühendatud avalike sõlmedega, vaid loovad oma. Kui palju protsente kasutajatest arvatavasti seda teeb? Õigesti, 0. Osaliselt oleme selle probleemi lahendanud Tor-versiooniga messengerist.

Oleme tõestanud, et plokiahelal põhinev sõnumiteenus võib eksisteerida. Varem tehti 2012. aastal ainult üks katse — bitmessage, mis ebaõnnestus pika sõnumite edastamise aja, protsessorikoormuse ja mobiilirakenduste puudumise tõttu.

Ja skeptitsism tuleneb sellest, et plokiahelal põhinevad sõnumiteenused ületavad aja — inimesed ei ole veel valmis võtma vastutust oma konto eest, isikliku teabe omamine ei ole veel populaarne ning tehnoloogia ei suuda plokiahelal kõrgeid kiirusnäitajaid tagada. Järgmised sammud toovad meie projekti tehnoloogiliselt arenenumad alternatiivid. Näete.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster