Com funciona un missatger descentralitzat a la cadena de blocs

A principis del 2017, vam començar a crear un missatger a la cadena de blocs [el nom i l'enllaç són al perfil] discutint els avantatges respecte als missatgers P2P clàssics.

Aprovat 2.5 any, i vam poder confirmar el nostre concepte: ara les aplicacions de missatgeria estan disponibles per a iOS, Web PWA, Windows, GNU/Linux, Mac OS i Android.

Avui us explicarem com funciona el missatger de cadena de blocs i com poden funcionar les aplicacions de client amb la seva API.
Com funciona un missatger descentralitzat a la cadena de blocs

Volíem que la cadena de blocs resolgués els problemes de seguretat i privadesa dels missatgers P2P clàssics:

  • Un clic per crear un compte: sense telèfons ni correus electrònics, sense accés a llibretes d'adreces o geolocalitzacions.
  • Els interlocutors no estableixen mai connexions directes; tota comunicació es fa a través d'un sistema distribuït de nodes. Les adreces IP dels usuaris no són accessibles entre si.
  • Tots els missatges estan xifrats d'extrem a extrem curve25519xsalsa20poly1305. Sembla que això no sorprendrà a ningú, però el nostre codi font és obert.
  • L'atac MITM està exclòs: cada missatge és una transacció i està signat per Ed25519 EdDSA.
  • El missatge acaba en el seu propi bloc. Coherència i timestamp No pots arreglar els blocs, i per tant l'ordre dels missatges.
  • "No vaig dir això" no funcionarà amb missatges a la cadena de blocs.
  • No hi ha cap estructura central que controli l'"autenticitat" d'un missatge. Això es fa mitjançant un sistema distribuït de nodes basat en el consens, i és propietat dels usuaris.
  • Impossibilitat de censura: els comptes no es poden bloquejar i els missatges no es poden suprimir.
  • Blockchain 2FA és una alternativa a l'infernal 2FA per SMS, va arruïnar molta salut.
  • La possibilitat d'obtenir totes les teves converses des de qualsevol dispositiu en qualsevol moment significa que no has d'emmagatzemar converses localment.
  • Confirmació del lliurament del missatge. No al dispositiu de l'usuari, sinó a la xarxa. Bàsicament, es tracta de la confirmació de la capacitat del destinatari per llegir el vostre missatge. Aquesta és una característica útil per enviar notificacions crítiques.

Els avantatges de Blockchain també inclouen una estreta integració amb les criptomonedes Ethereum, Dogecoin, Lisk, Dash, Bitcoin (aquest encara està en curs) i la possibilitat d'enviar fitxes als xats. Fins i tot vam fer un intercanviador criptogràfic integrat.

I després, com funciona tot.

Un missatge és una transacció

Tothom ja està acostumat al fet que les transaccions a la cadena de blocs transfereixen fitxes (monedes) d'un usuari a un altre. Com Bitcoin. Hem creat un tipus especial de transacció per transmetre missatges.

Per enviar un missatge en un missatger a la cadena de blocs, heu de seguir diversos passos:

  1. Xifra el text del missatge
  2. Posa text xifrat en una transacció
  3. Signa la transacció
  4. Envia una transacció a qualsevol node de xarxa
  5. Un sistema distribuït de nodes determina l'"autenticitat" d'un missatge
  6. Si tot està bé, la transacció amb el missatge s'inclou al bloc següent
  7. El destinatari recupera la transacció del missatge i desxifra

Els passos 1-3 i 7 es realitzen localment al client, i els passos 5-6 es realitzen als amfitrions.

Xifratge de missatges

El missatge es xifra amb la clau privada del remitent i la clau pública del destinatari. Prendrem la clau pública de la xarxa, però per a això cal que el compte del destinatari estigui inicialitzat, és a dir, tenir almenys una transacció. Podeu utilitzar una sol·licitud REST GET /api/accounts/getPublicKey?address={ADAMANT address}, i en carregar xats, les claus públiques dels interlocutors ja estaran disponibles.

Com funciona un missatger descentralitzat a la cadena de blocs

El missatger xifra els missatges mitjançant l'algoritme curve25519xsalsa20poly1305 (Caixa de NaCl). Com que el compte conté claus Ed25519, per formar una caixa, primer s'han de convertir les claus a Curve25519 Diffie-Hellman.

Aquí teniu un exemple 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)
  }
}

Formar una transacció amb un missatge

La transacció té la següent estructura general:

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

Per a una transacció de missatges, el més important és asset - cal col·locar un missatge a l'objecte chat amb estructura:

  • message - desar el missatge xifrat
  • own_message -noce
  • type - tipus de missatge

Els missatges també es divideixen en tipus. Bàsicament, el paràmetre type t'explica com entendre'l message. Podeu enviar només un text o podeu enviar un objecte amb coses interessants a dins; per exemple, així és com el missatger fa transferències de criptomoneda als xats.

Com a resultat, creem una transacció:

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

Signatura de la transacció

Per garantir que tothom tingui confiança en l'autenticitat de l'emissor i el destinatari, l'hora d'enviament i el contingut del missatge, la transacció està signada. Una signatura digital us permet verificar l'autenticitat d'una transacció mitjançant una clau pública; no és necessària una clau privada per a això.

Però la signatura en si es realitza mitjançant la clau privada:

Com funciona un missatger descentralitzat a la cadena de blocs

El diagrama mostra que primer fem hash la transacció amb SHA-256 i després la signem Ed25519 EdDSA i obtenir una signatura signature, i l'identificador de la transacció forma part del hash SHA-256.

Exemple d'implementació:

1 — Formeu un bloc de dades, inclòs un missatge

/**
 * 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 - Compteu SHA-256 del bloc de dades

/**
 * 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 — Signa la transacció

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

Enviament d'una transacció amb un missatge a un node de xarxa

Com que la xarxa està descentralitzada, qualsevol dels nodes amb una API oberta ho farà. Realització d'una sol·licitud POST al punt final api/transactions:

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

En resposta rebrem un identificador de transacció del tipus

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

Validació de la transacció

Un sistema distribuït de nodes, basat en el consens, determina l'"autenticitat" del missatge de la transacció. De qui i a qui, quan, si el missatge s'ha substituït per un altre i si s'ha indicat correctament l'hora d'enviament. Aquest és un avantatge molt important de la cadena de blocs: no hi ha cap estructura central responsable de la verificació i la seqüència de missatges i el seu contingut no es poden falsificar.

En primer lloc, un node comprova la precisió i després l'envia a altres: si la majoria diu que tot està en ordre, la transacció s'inclourà al següent bloc de la cadena, això és consens.

Com funciona un missatger descentralitzat a la cadena de blocs

La part del codi del node responsable de les comprovacions es pot veure a GitHub: validator.js и verificar.js. Sí, el node s'executa a Node.js.

Incloure una transacció amb un missatge en un bloc

Si s'arriba a un consens, la transacció amb el nostre missatge s'inclourà al bloc següent juntament amb altres transaccions vàlides.

Els blocs tenen una seqüència estricta i cada bloc posterior es forma a partir dels hashes dels blocs anteriors.

Com funciona un missatger descentralitzat a la cadena de blocs

La qüestió és que el nostre missatge també s'inclou en aquesta seqüència i no es pot "reordenar". Si diversos missatges cauen en un bloc, el seu ordre serà determinat per timestamp missatges.

Lectura de missatges

L'aplicació de missatgeria recupera les transaccions de la cadena de blocs que s'envien al destinatari. Per a això vam fer un punt final api/chatrooms.

Totes les transaccions estan disponibles per a tothom: podeu rebre missatges xifrats. Però només el destinatari pot desxifrar mitjançant la seva clau privada i la clau pública del remitent:

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

I què més?

Com que els missatges s'entreguen d'aquesta manera en uns 5 segons (és el moment en què apareix un nou bloc de xarxa), hem creat una connexió de sòcol client a node i node a node. Quan un node rep una nova transacció, comprova la seva validesa i la reenvia a altres nodes. La transacció està disponible per als clients de missatgeria fins i tot abans que es produeixi el consens i la inclusió al bloc. D'aquesta manera enviarem missatges a l'instant, igual que els missatgers instantanis habituals.

Per emmagatzemar la llibreta d'adreces, hem fet KVS - Key-Value Storage - aquest és un altre tipus de transacció en què asset no és NaCl-box el que està xifrat, però Caixa secreta de NaCl. Així és com el missatger emmagatzema altres dades.

La transferència de fitxers/imatges i xats de grup encara requereix molta feina. Per descomptat, en un format d'error i error, això es pot "enfonsar" ràpidament, però volem mantenir el mateix nivell de privadesa.

Sí, encara queda feina per fer; idealment, la privadesa real suposa que els usuaris no es connectaran als nodes de la xarxa pública, sinó que crearan els seus. Quin percentatge d'usuaris creus que fa això? Així és, 0. Hem pogut resoldre parcialment aquest problema amb la versió Tor del messenger.

Hem demostrat que pot existir un missatger a la cadena de blocs. Abans, només hi va haver un intent el 2012: missatge de bits, que va fallar a causa dels llargs temps de lliurament dels missatges, la càrrega de la CPU i la manca d'aplicacions mòbils.

I l'escepticisme es deu al fet que els missatgers de la cadena de blocs estan avançats al seu temps: la gent no està preparada per assumir la responsabilitat del seu compte, la propietat d'informació personal encara no és una tendència i la tecnologia no permet altes velocitats a la cadena de blocs. A continuació apareixeran més anàlegs tecnològics del nostre projecte. Veuràs.

Font: www.habr.com

Afegeix comentari