Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

Komence de 2017, ni komencis krei mesaĝiston sur la blokĉeno [nomo kaj ligo estas en la profilo] diskutante la avantaĝojn super klasikaj P2P mesaĝistoj.

Foriris 2.5 jaro, kaj ni povis konfirmi nian koncepton: mesaĝaj aplikaĵoj nun disponeblas por iOS, Web PWA, Vindozo, GNU/Linukso, Mac OS kaj Android.

Hodiaŭ ni rakontos al vi kiel funkcias la blokĉena mesaĝisto kaj kiel klientaj aplikoj povas funkcii kun ĝia API.
Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

Ni volis, ke la blokĉeno solvu la problemojn pri sekureco kaj privateco de klasikaj P2P mesaĝistoj:

  • Unu klako por krei konton - neniuj telefonoj aŭ retpoŝtoj, neniu aliro al adreslibroj aŭ geolokigoj.
  • La interparolantoj neniam establas rektajn ligojn; ĉiu komunikado okazas per distribuita sistemo de nodoj. La IP-adresoj de uzantoj ne estas alireblaj unu por la alia.
  • Ĉiuj mesaĝoj estas ĉifritaj End-al-End curve25519xsalsa20poly1305. Ŝajnas, ke ĉi tio ne surprizos iun ajn, sed nia fontkodo estas malfermita.
  • MITM-atako estas ekskludita - ĉiu mesaĝo estas transakcio kaj estas subskribita de Ed25519 EdDSA.
  • La mesaĝo finiĝas en sia propra bloko. Konsistenco kaj timestamp Vi ne povas ripari la blokojn, kaj tial la ordon de la mesaĝoj.
  • "Mi ne diris tion" ne funkcios kun mesaĝoj sur la blokĉeno.
  • Ne ekzistas centra strukturo, kiu kontrolas la "aŭtentecon" de mesaĝo. Ĉi tio estas farita per distribuita sistemo de nodoj bazitaj sur konsento, kaj ĝi estas posedata de la uzantoj.
  • Neebleco de cenzuro - kontoj ne povas esti blokitaj kaj mesaĝoj ne povas esti forigitaj.
  • Blockchain 2FA estas alternativo al la infera 2FA per SMS, ruinigis multe da sano.
  • La kapablo ricevi ĉiujn viajn konversaciojn de iu ajn aparato iam ajn signifas, ke vi tute ne devas konservi konversaciojn loke.
  • Konfirmo de livero de mesaĝoj. Ne al la aparato de la uzanto, sed al la reto. Esence, ĉi tio estas konfirmo de la kapablo de la ricevanto legi vian mesaĝon. Ĉi tio estas utila funkcio por sendi kritikajn sciigojn.

La avantaĝoj de Blockchain ankaŭ inkluzivas proksiman integriĝon kun la kriptaj moneroj Ethereum, Dogecoin, Lisk, Dash, Bitcoin (ĉi tiu ankoraŭ estas en progreso) kaj la kapablon sendi ĵetonojn en babilejoj. Ni eĉ faris enkonstruitan kriptan interŝanĝilon.

Kaj tiam - kiel ĉio funkcias.

Mesaĝo estas transakcio

Ĉiuj jam kutimas al tio, ke transakcioj en la blokĉeno transdonas ĵetonojn (moneroj) de unu uzanto al alia. Kiel Bitcoin. Ni kreis specialan tipon de transakcio por transdoni mesaĝojn.

Por sendi mesaĝon en mesaĝisto sur la blokĉeno, vi devas trairi plurajn paŝojn:

  1. Ĉifri mesaĝan tekston
  2. Metu ĉifrtekston en transakcion
  3. Subskribu la transakcion
  4. Sendu transakcion al iu ajn retnodo
  5. Distribuita sistemo de nodoj determinas la "aŭtentecon" de mesaĝo
  6. Se ĉio estas en ordo, la transakcio kun la mesaĝo estas inkluzivita en la sekva bloko
  7. La ricevanto prenas la mesaĝtransakcion kaj deĉifras

Paŝoj 1-3 kaj 7 estas faritaj loke sur la kliento, kaj ŝtupoj 5-6 estas faritaj sur la gastigantoj.

Mesaĝa ĉifrado

La mesaĝo estas ĉifrita per la privata ŝlosilo de la sendinto kaj la publika ŝlosilo de la ricevanto. Ni prenos la publikan ŝlosilon de la reto, sed por tio, la konto de la ricevanto devas esti pravigita, tio estas, havi almenaŭ unu transakcion. Vi povas uzi REST-peton GET /api/accounts/getPublicKey?address={ADAMANT address}, kaj dum ŝarĝo de babilejoj, la publikaj ŝlosiloj de la interparolantoj jam estos disponeblaj.

Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

La mesaĝisto ĉifras mesaĝojn per la kurbo25519xsalsa20poly1305 algoritmo (NaCl-Skatolo). Ĉar la konto enhavas Ed25519-ŝlosilojn, por formi skatolon, la ŝlosiloj unue devas esti konvertitaj al Curve25519 Diffie-Hellman.

Jen ekzemplo en 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)
  }
}

Formante transakcion kun mesaĝo

La transakcio havas la sekvan ĝeneralan strukturon:

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

Por mesaĝtransakcio, la plej grava afero estas asset - vi devas meti mesaĝon en la objekton chat kun strukturo:

  • message - konservu la ĉifritan mesaĝon
  • own_message -nonce
  • type — mesaĝo tipo

Mesaĝoj ankaŭ estas dividitaj en tipojn. Esence, la parametro type diras al vi kiel kompreni message. Vi povas sendi nur tekston, aŭ vi povas sendi objekton kun interesaj aferoj interne - ekzemple, jen kiel la mesaĝisto faras kriptajn monertranspagojn en babilejoj.

Kiel rezulto, ni kreas transakcion:

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

Transakcia subskribo

Por certigi, ke ĉiuj estas certaj pri la aŭtentikeco de la sendinto kaj ricevanto, la tempo de sendo kaj la enhavo de la mesaĝo, la transakcio estas subskribita. Cifereca subskribo permesas vin kontroli la aŭtentikecon de transakcio per publika ŝlosilo - privata ŝlosilo ne estas necesa por tio.

Sed la subskribo mem estas farita per la privata ŝlosilo:

Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

La diagramo montras, ke ni unue haŝas la transakcion kun SHA-256 kaj poste subskribas ĝin Ed25519 EdDSA kaj ricevu subskribon signature, kaj la transakcia ID estas parto de la SHA-256 hash.

Ekzempla efektivigo:

1 — Formu datumblokon, inkluzive de mesaĝo

/**
 * 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 - Kalkulo SHA-256 el la datumbloko

/**
 * 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 — Subskribu la transakcion

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

Sendante transakcion kun mesaĝo al retnodo

Ĉar la reto estas malcentralizita, iu ajn el la nodoj kun malfermita API faros. Farante POST-peton al la finpunkto api/transactions:

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

Responde ni ricevos transakcian ID de la tipo

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

Transakcia Valido

Distribuita sistemo de nodoj, bazita sur interkonsento, determinas la "aŭtentecon" de la transakcia mesaĝo. De kiu kaj al kiu, kiam, ĉu la mesaĝo estis anstataŭigita per alia, kaj ĉu la horo de sendo estis ĝuste indikita. Ĉi tio estas tre grava avantaĝo de la blokĉeno - ne ekzistas centra strukturo, kiu respondecas pri kontrolo, kaj la sinsekvo de mesaĝoj kaj ilia enhavo ne povas esti falsitaj.

Unue, unu nodo kontrolas la precizecon, kaj poste sendas ĝin al aliaj - se la plimulto diras, ke ĉio estas en ordo, la transakcio estos inkluzivita en la sekva bloko de la ĉeno - ĉi tio estas konsento.

Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

La parto de la noda kodo, kiu respondecas pri kontroloj, povas esti vidita sur GitHub - validigilo.js и verify.js. Jes, la nodo funkcias per Node.js.

Inkluzive de transakcio kun mesaĝo en bloko

Se konsento estas atingita, la transakcio kun nia mesaĝo estos inkluzivita en la sekva bloko kune kun aliaj validaj transakcioj.

Blokoj havas striktan sekvencon, kaj ĉiu posta bloko estas formita surbaze de la hashoj de antaŭaj blokoj.

Kiel funkcias malcentralizita mesaĝisto sur la blokĉeno?

La punkto estas, ke nia mesaĝo ankaŭ estas inkluzivita en ĉi tiu sinsekvo kaj ne povas esti "rearanĝita". Se pluraj mesaĝoj falas en blokon, ilia ordo estos determinita de timestamp mesaĝojn.

Legante mesaĝojn

La mesaĝa aplikaĵo prenas transakciojn de la blokĉeno, kiuj estas senditaj al la ricevanto. Por tio ni faris finpunkton api/chatrooms.

Ĉiuj transakcioj estas disponeblaj por ĉiuj - vi povas ricevi ĉifritajn mesaĝojn. Sed nur la ricevanto povas deĉifri per sia privata ŝlosilo kaj la publika ŝlosilo de la sendinto:

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

Kaj kio alia?

Ĉar mesaĝoj estas liveritaj tiamaniere en ĉirkaŭ 5 sekundoj - jen la tempo kiam nova reto bloko aperas - ni elpensis klient-al-nodo kaj nodo-al-nodo ingo konekto. Kiam nodo ricevas novan transakcion, ĝi kontrolas sian validecon kaj plusendas ĝin al aliaj nodoj. La transakcio haveblas al mesaĝistaj klientoj eĉ antaŭ ol okazas konsento kaj inkludo en la bloko. Tiel ni liveros mesaĝojn tuj, same kiel kutimaj tujmesaĝiloj.

Por konservi la adreslibron, ni faris KVS - Key-Value Storage - ĉi tio estas alia speco de transakcio en kiu asset ĝi ne estas NaCl-kesto kiu estas ĉifrita, sed NaCl-sekreta kesto. Tiel la mesaĝisto konservas aliajn datumojn.

Transdono de dosieroj/bildoj kaj grupaj babiloj ankoraŭ postulas multan laboron. Kompreneble, en la fuŝo-kaj-fuŝformato ĉi tio povas esti "faŭligita" rapide, sed ni volas konservi la saman nivelon de privateco.

Jes, estas ankoraŭ laboro farenda - ideale, reala privateco supozas, ke uzantoj ne konektos al publikaj retaj nodoj, sed altigos sian propran. Kia procento de uzantoj laŭ vi faras ĉi tion? Ĝuste, 0. Ni povis parte solvi ĉi tiun problemon per la Tor-versio de la mesaĝisto.

Ni pruvis, ke mesaĝisto sur la blokĉeno povas ekzisti. Antaŭe, estis nur unu provo en 2012 - bitmesaĝo, kiu malsukcesis pro longaj mesaĝaj livertempoj, CPU-ŝarĝo kaj manko de moveblaj aplikoj.

Kaj skeptiko ŝuldiĝas al la fakto, ke mesaĝistoj sur la blokĉeno estas antaŭ sia tempo - homoj ne pretas preni respondecon pri sia konto, posedi personajn informojn ankoraŭ ne estas tendenco, kaj teknologio ne permesas altajn rapidojn sur la blokĉeno. Pli da teknologiaj analogoj de nia projekto aperos poste. Vi vidos.

fonto: www.habr.com

Aldoni komenton