Como funciona un mensaxeiro descentralizado na cadea de bloques?

A principios de 2017, comezamos a crear un mensaxeiro na cadea de bloques [nome e ligazón están no perfil] discutindo as vantaxes sobre os clásicos mensaxeiros P2P.

Aprobado 2.5 ano, e puidemos confirmar o noso concepto: as aplicacións de mensaxería xa están dispoñibles para iOS, Web PWA, Windows, GNU/Linux, Mac OS e Android.

Hoxe contarémosche como funciona o blockchain messenger e como poden funcionar as aplicacións cliente coa súa API.
Como funciona un mensaxeiro descentralizado na cadea de bloques?

Queriamos que a cadea de bloques resolva os problemas de seguridade e privacidade dos clásicos mensaxeiros P2P:

  • Un clic para crear unha conta: sen teléfonos nin correos electrónicos, sen acceso a axendas de enderezos ou xeolocalizacións.
  • Os interlocutores nunca establecen conexións directas; toda a comunicación realízase a través dun sistema distribuído de nodos. Os enderezos IP dos usuarios non son accesibles entre si.
  • Todas as mensaxes están cifradas de extremo a extremo curve25519xsalsa20poly1305. Parece que isto non sorprenderá a ninguén, pero o noso código fonte está aberto.
  • O ataque MITM está excluído: cada mensaxe é unha transacción e está asinada por Ed25519 EdDSA.
  • A mensaxe acaba no seu propio bloque. Consistencia e timestamp Non podes corrixir os bloqueos e, polo tanto, a orde das mensaxes.
  • "Non dixen iso" non funcionará con mensaxes na cadea de bloques.
  • Non hai unha estrutura central que verifique a "autenticidade" dunha mensaxe. Isto faise por un sistema distribuído de nodos baseado no consenso, e é propiedade dos usuarios.
  • Imposibilidade de censura: as contas non se poden bloquear e as mensaxes non se poden eliminar.
  • Blockchain 2FA é unha alternativa ao infernal 2FA a través de SMS, estragou moita saúde.
  • A posibilidade de obter todas as túas conversas desde calquera dispositivo en calquera momento significa que non tes que almacenar as conversas localmente.
  • Confirmación da entrega da mensaxe. Non ao dispositivo do usuario, senón á rede. Esencialmente, esta é a confirmación da capacidade do destinatario para ler a túa mensaxe. Esta é unha función útil para enviar notificacións críticas.

Os beneficios de Blockchain tamén inclúen unha estreita integración coas criptomoedas Ethereum, Dogecoin, Lisk, Dash, Bitcoin (este aínda está en curso) e a posibilidade de enviar tokens nos chats. Incluso fixemos un intercambiador criptográfico integrado.

E entón - como funciona todo.

Unha mensaxe é unha transacción

Todo o mundo xa está afeito ao feito de que as transaccións na cadea de bloques transfiran tokens (moedas) dun usuario a outro. Como Bitcoin. Creamos un tipo especial de transacción para transmitir mensaxes.

Para enviar unha mensaxe nun messenger na cadea de bloques, debes pasar por varios pasos:

  1. Cifrar o texto da mensaxe
  2. Pon texto cifrado nunha transacción
  3. Asina a transacción
  4. Enviar unha transacción a calquera nodo da rede
  5. Un sistema distribuído de nós determina a "autenticidade" dunha mensaxe
  6. Se todo está ben, a transacción coa mensaxe inclúese no seguinte bloque
  7. O destinatario recupera a transacción da mensaxe e descifra

Os pasos 1-3 e 7 realízanse localmente no cliente e os pasos 5-6 realízanse nos hosts.

Cifrado de mensaxes

A mensaxe está cifrada coa clave privada do remitente e a clave pública do destinatario. Colleremos a chave pública da rede, pero para iso, a conta do destinatario debe estar inicializada, é dicir, ter polo menos unha transacción. Podes usar unha solicitude REST GET /api/accounts/getPublicKey?address={ADAMANT address}, e ao cargar chats xa estarán dispoñibles as claves públicas dos interlocutores.

Como funciona un mensaxeiro descentralizado na cadea de bloques?

O messenger cifra as mensaxes usando o algoritmo curve25519xsalsa20poly1305 (Caixa de NaCl). Dado que a conta contén claves Ed25519, para formar unha caixa, as claves deben converterse primeiro en Curve25519 Diffie-Hellman.

Aquí tes un exemplo 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 unha transacción cunha mensaxe

A transacción ten a seguinte estrutura xeral:

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

Para unha transacción de mensaxes, o máis importante é asset - cómpre poñer unha mensaxe no obxecto chat con estrutura:

  • message - garda a mensaxe cifrada
  • own_message -nonce
  • type - tipo de mensaxe

As mensaxes tamén se dividen en tipos. Esencialmente, o parámetro type diche como entender message. Podes enviar só un texto ou podes enviar un obxecto con cousas interesantes dentro - por exemplo, así é como o mensaxeiro realiza transferencias de criptomonedas nos chats.

Como resultado, creamos unha transacción:

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

Sinatura da transacción

Para garantir que todos confían na autenticidade do remitente e do destinatario, a hora de envío e o contido da mensaxe, a transacción está asinada. Unha sinatura dixital permítelle verificar a autenticidade dunha transacción mediante unha chave pública; para iso non é necesaria unha clave privada.

Pero a sinatura en si realízase mediante a clave privada:

Como funciona un mensaxeiro descentralizado na cadea de bloques?

O diagrama mostra que primeiro haxemos a transacción con SHA-256 e despois asinamos Ed25519 EdDSA e conseguir unha sinatura signature, e o ID da transacción forma parte do hash SHA-256.

Exemplo de implementación:

1 — Forme un bloque de datos, incluída unha mensaxe

/**
 * 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 - Conta SHA-256 do bloque de datos

/**
 * 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 - Asina a transacción

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

Envío dunha transacción cunha mensaxe a un nodo da rede

Dado que a rede está descentralizada, calquera dos nodos cunha API aberta servirá. Facendo unha solicitude POST ao punto final api/transactions:

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

En resposta, recibiremos un ID de transacción do tipo

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

Validación de transaccións

Un sistema distribuído de nós, baseado no consenso, determina a "autenticidade" da mensaxe da transacción. De quen e a quen, cando, se a mensaxe foi substituída por outra e se se indicou correctamente a hora de envío. Esta é unha vantaxe moi importante da cadea de bloques: non hai unha estrutura central responsable da verificación e non se pode falsificar a secuencia de mensaxes e o seu contido.

En primeiro lugar, un nodo comproba a precisión e, a continuación, envíao a outros - se a maioría di que todo está en orde, a transacción incluirase no seguinte bloque da cadea - este é o consenso.

Como funciona un mensaxeiro descentralizado na cadea de bloques?

A parte do código do nodo responsable das comprobacións pódese ver en GitHub - validator.js и verificar.js. Si, o nodo execútase en Node.js.

Incluír unha transacción cunha mensaxe nun bloque

Se se alcanza o consenso, a transacción coa nosa mensaxe incluirase no seguinte bloque xunto con outras transaccións válidas.

Os bloques teñen unha secuencia estrita e cada bloque posterior fórmase en función dos hash dos bloques anteriores.

Como funciona un mensaxeiro descentralizado na cadea de bloques?

A cuestión é que a nosa mensaxe tamén se inclúe nesta secuencia e non se pode "reorganizar". Se varias mensaxes caen nun bloque, a súa orde estará determinada por timestamp mensaxes.

Lectura de mensaxes

A aplicación de mensaxería recupera as transaccións da cadea de bloques que se envían ao destinatario. Para iso fixemos un punto final api/chatrooms.

Todas as transaccións están dispoñibles para todos: podes recibir mensaxes cifradas. Pero só o destinatario pode descifrar usando a súa clave privada e a clave pública do remitente:

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

E que máis?

Dado que as mensaxes se envían deste xeito nuns 5 segundos (este é o momento en que aparece un novo bloque de rede), creamos unha conexión de socket de cliente a nodo e de nodo a nodo. Cando un nodo recibe unha nova transacción, comproba a súa validez e pásaa a outros nodos. A transacción está dispoñible para os clientes de mensaxería incluso antes de que se produza o consenso e a inclusión no bloque. Deste xeito, enviaremos mensaxes ao instante, igual que os mensaxeiros instantáneos habituais.

Para almacenar a axenda de enderezos, fixemos KVS - Key-Value Storage - este é outro tipo de transacción na que asset non é NaCl-box o que está cifrado, pero Caixa secreta de NaCl. Así é como o messenger almacena outros datos.

As transferencias de ficheiros/imaxes e os chats en grupo aínda requiren moito traballo. Por suposto, no formato de erros e erros, isto pode ser "torrado" rapidamente, pero queremos manter o mesmo nivel de privacidade.

Si, aínda queda traballo por facer; idealmente, a privacidade real supón que os usuarios non se conectarán aos nodos da rede pública, senón que elevarán os seus. Que porcentaxe de usuarios cres que fai isto? Así é, 0. Puidemos resolver parcialmente este problema coa versión Tor do messenger.

Demostramos que pode existir un mensaxeiro na cadea de bloques. Anteriormente, só houbo un intento en 2012: mensaxe bit, que fallou debido aos longos tempos de entrega das mensaxes, á carga da CPU e á falta de aplicacións móbiles.

E o escepticismo débese ao feito de que os mensaxeiros da cadea de bloques están adiantados ao seu tempo: a xente non está preparada para asumir a responsabilidade da súa conta, ter información persoal aínda non é unha tendencia e a tecnoloxía non permite altas velocidades na cadea de bloques. A continuación aparecerán máis análogos tecnolóxicos do noso proxecto. Xa verás.

Fonte: www.habr.com

Engadir un comentario