Cómo funciona un mensajero descentralizado en la blockchain.

A principios de 2017 comenzamos a crear un mensajero en la blockchain [el nombre y el enlace están en el perfil] discutiendo las ventajas sobre los mensajeros P2P clásicos.

Han pasado 2.5 años, y hemos logrado validar nuestro concepto: ahora las aplicaciones del mensajero están disponibles para iOS, Web PWA, Windows, GNU/Linux, Mac OS y Android.

Hoy hablaremos sobre cómo funciona el mensajero en la blockchain y cómo las aplicaciones del cliente interactúan con su API.
Cómo funciona un mensajero descentralizado en la blockchain.

Queríamos que la blockchain resolviera los problemas de seguridad y privacidad de los mensajeros P2P clásicos:

  • Un clic para crear una cuenta — sin teléfonos ni correos electrónicos, sin acceso a las listas de contactos ni a las geolocalizaciones.
  • Las partes nunca establecen conexiones directas, toda la comunicación pasa a través de un sistema distribuido de nodos. Las direcciones IP de los usuarios no son accesibles entre sí.
  • Todos los mensajes están cifrados de extremo a extremo con curve25519xsalsa20poly1305. Parece que esto ya no sorprende a nadie, pero nosotros tenemos el código fuente abierto.
  • Los ataques MITM están excluidos — cada mensaje es una transacción y se firma con Ed25519 EdDSA.
  • El mensaje entra en su bloque. La secuencia y timestamp los bloques no se pueden alterar, por lo tanto el orden de los mensajes tampoco.
  • “No dije eso” no funcionará con los mensajes en la blockchain.
  • No existe una estructura central que realice verificaciones de 'veracidad' del mensaje. Esto lo hace un sistema distribuido de nodos basado en consenso, y pertenece a los usuarios.
  • La imposibilidad de censura — las cuentas no pueden ser bloqueadas y los mensajes no pueden ser eliminados.
  • Blockchain 2FA — una alternativa al horrible 2FA por SMS, que ha causado muchos problemas de salud.
  • La posibilidad de acceder a todos tus diálogos desde cualquier dispositivo en cualquier momento — esto significa que no es necesario almacenar diálogos localmente en absoluto.
  • Confirmación de entrega de mensajes. No al dispositivo del usuario, sino a la red. En esencia, esto es una confirmación de que el destinatario puede leer tu mensaje. Esta es una característica útil para enviar notificaciones críticas.

También de las ventajas de la blockchain, la integración estrecha con las criptomonedas Ethereum, Dogecoin, Lisk, Dash, Bitcoin (esta aún está en proceso) y la posibilidad de enviar tokens en los chats. Incluso hemos creado un intercambio cripto integrado.

Y a partir de ahí, cómo funciona todo esto.

Un mensaje es una transacción.

Todos ya están acostumbrados a que las transacciones en la blockchain transmiten tokens (monedas) de un usuario a otro. Como en Bitcoin. Nosotros hemos creado un tipo especial de transacciones para la transmisión de mensajes.

Para enviar un mensaje en el mensajero de la blockchain, hay que seguir varios pasos:

  1. Cifrar el texto del mensaje.
  2. Incluir texto cifrado en la transacción
  3. Firmar la transacción
  4. Enviar la transacción a cualquier nodo de la red
  5. El sistema distribuido de nodos determina la 'validez' del mensaje
  6. Si todo está OK, la transacción con el mensaje se incluye en el siguiente bloque
  7. El receptor extrae la transacción con el mensaje y lo descifra

Las etapas 1–3 y 7 se realizan localmente en el cliente, mientras que 5–6 se realizan en los nodos de la red.

Cifrado del mensaje

El mensaje se cifra con la clave privada del remitente y la clave pública del receptor. La clave pública la obtenemos de la red, pero para ello la cuenta del receptor debe estar inicializada, es decir, tener al menos una transacción. Se puede usar una solicitud REST GET /api/accounts/getPublicKey?address={ADAMANT address}, y al cargar chats, las claves públicas de los interlocutores ya estarán disponibles.

Cómo funciona un mensajero descentralizado en la blockchain.

El mensajero cifra los mensajes utilizando el algoritmo curve25519xsalsa20poly1305 (NaCl Box). Dado que la cuenta contiene claves Ed25519, las claves deben transformarse a Curve25519 Diffie-Hellman para formar el box.

Aquí hay un ejemplo 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)
  }
}

Formación de la transacción con el mensaje

La transacción tiene la siguiente 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": {}
}

Para la transacción con mensaje, el valor más importante es asset — donde debe incluirse el mensaje en un objeto chat con la siguiente estructura:

  • message — mantener el mensaje cifrado
  • own_message — nonce
  • tipo — tipo de mensaje

Los mensajes también se dividen en tipos. Esencialmente, el parámetro tipo indica cómo interpretar message. Se puede enviar simplemente texto o un objeto con detalles internos; por ejemplo, así es como el mensajero realiza las transferencias de criptomonedas en los chats.

Al final, formamos la 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": "habrá una firma aquí"
  }
}

Firma de la transacción

Para que todos estén seguros de la autenticidad del remitente y del destinatario, así como del momento de envío y del contenido del mensaje, la transacción se firma. Una firma digital permite verificar la autenticidad de la transacción mediante una clave pública; no se necesita la clave privada para esto.

La firma en sí se realiza con la clave privada:

Cómo funciona un mensajero descentralizado en la blockchain.

Como muestra el esquema, primero hashamos la transacción con SHA-256 y luego la firmamos. Ed25519 EdDSA y obtenemos la firma firma, y el identificador de la transacción es parte del hash SHA-256.

Ejemplo de implementación:

1 — Formamos el bloque de datos, incluyendo el mensaje

/**
 * 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 — Calculamos SHA-256 del 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 — Firmamos la transacción

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

/**
 * Crea una firma basada en un hash y un par de claves.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} firma
 *\/ 
adamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Envío de la transacción con un mensaje al nodo de la red

Dado que la red es descentralizada, cualquier nodo con API abierta es adecuado. Hacemos una solicitud POST al endpoint api/transactions:

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

Como respuesta obtendremos el ID de la transacción de tipo

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

Verificación de la autenticidad de la transacción

El sistema distribuido de nodos basado en consenso determina la “autenticidad” del mensaje de la transacción. De quién y a quién, cuándo, si no se reemplazó el mensaje por otro, y si se indicó correctamente la hora de envío. Esta es una de las ventajas más importantes de la blockchain: no hay una estructura central que se encargue de las verificaciones, y la secuencia de mensajes y su contenido no se pueden falsificar.

Primero, la autenticidad la verifica un nodo, y luego la comunica a otros; si la mayoría dice que todo está en orden, la transacción se incluirá en el siguiente bloque de la cadena — eso es el consenso.

Cómo funciona un mensajero descentralizado en la blockchain.

Una parte del código del nodo que se encarga de las verificaciones se puede ver en GitHub — validator.js y verify.js. Ah, el nodo funciona en Node.js.

Incluimos la transacción con el mensaje en un bloque

Si se alcanza el consenso, la transacción con nuestro mensaje se incluirá en el siguiente bloque junto con otras transacciones autenticadas.

Los bloques tienen una secuencia estricta, y cada bloque siguiente se forma sobre la base de los hashes de los bloques anteriores.

Cómo funciona un mensajero descentralizado en la blockchain.

La esencia es que nuestro mensaje también está incluido en esta secuencia y no puede ser "reordenado". Si varios mensajes entran en un bloque, su orden se determinará por timestamp mensajes.

Lectura de mensajes

La aplicación de mensajería extrae transacciones de la cadena de bloques que se envían al destinatario. Para ello, hemos creado un endpoint api/chatrooms.

Todas las transacciones están disponibles para todos: se pueden obtener mensajes cifrados. Sin embargo, solo el receptor podrá descifrarlos con su clave privada y la clave pública del 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) : ''
}

¿Y qué más?

Dado que los mensajes se entregan de esta manera en aproximadamente 5 segundos —el tiempo que tarda un nuevo bloque en la red— ideamos una conexión socket cliente-nodo y nodo-nodo. Cuando un nodo recibe una nueva transacción, verifica su validez y la transmite a otros nodos. La transacción está disponible para los clientes de mensajería antes de que se alcance el consenso y se incluya en el bloque. Así podremos entregar mensajes instantáneamente, como los mensajeros habituales.

Para almacenar la agenda de direcciones, creamos KVS —almacenamiento clave-valor— que es otro tipo de transacciones, en el cual asset no se cifra NaCl-box, sino NaCl-secretbox. Así el mensajero almacena otros datos.

La transferencia de archivos/imágenes y los chats grupales requieren aún mucho trabajo. Por supuesto, en un formato improvisado se puede "atar" rápidamente, pero queremos mantener el mismo nivel de privacidad.

Sí, aún hay mucho por hacer: en un ideal real la privacidad supone que los usuarios no se conecten a nodos públicos de la red, sino que levanten los suyos. ¿Cuánto porcentaje de usuarios creen que hace esto? Correcto, 0. Parte de este problema hemos logrado resolver con la versión Tor del mensajero.

Hemos demostrado que un mensajero en la blockchain puede existir. Anteriormente solo hubo un intento en 2012 — bitmessage, que fracasó debido al prolongado tiempo de entrega de los mensajes, la carga en el procesador y la falta de aplicaciones móviles.

Y el escepticismo surge porque los mensajeros en la blockchain están adelantados a su tiempo: la gente no está lista para asumir la responsabilidad de su cuenta, la posesión de información personal aún no está en tendencia, y la tecnología no permite garantizar altas velocidades en la blockchain. Pronto aparecerán análogos más tecnológicos de nuestro proyecto. Ya lo verán.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster