Como funciona um mensageiro descentralizado na blockchain

No início de 2017, começamos a criar um mensageiro baseado em blockchain [o nome e o link estão no perfil], discutindo as vantagens em relação aos mensageiros P2P clássicos.

Passaram-se 2.5 anos, e conseguimos validar nosso conceito: agora estão disponíveis aplicativos do mensageiro para iOS, Web PWA, Windows, GNU/Linux, Mac OS e Android.

Hoje, vamos explicar como funciona o mensageiro em blockchain e como os aplicativos clientes interagem com sua API.
Como funciona um mensageiro descentralizado na blockchain

Queríamos que o blockchain resolvesse questões de segurança e privacidade dos mensageiros P2P clássicos:

  • Um clique para criar uma conta — sem telefones ou e-mails, sem acesso a contatos e geolocalizações.
  • Os interlocutores nunca estabelecem conexões diretas; toda comunicação ocorre através de um sistema distribuído de nós. Os endereços IP dos usuários não são acessíveis uns aos outros.
  • Todas as mensagens são criptografadas de ponta a ponta com curve25519xsalsa20poly1305. Pode parecer que isso não surpreende ninguém, mas nosso código-fonte é aberto.
  • Ataques MITM estão excluídos — cada mensagem é uma transação e é assinada com Ed25519 EdDSA.
  • A mensagem entra em seu bloco. A sequência e timestamp os blocos não podem ser alterados, e, portanto, a ordem das mensagens também não.
  • “Eu não disse isso” não colará com mensagens em blockchain.
  • Não há uma estrutura central que verifica a 'veracidade' da mensagem. Isso é feito pelo sistema distribuído de nós com base em consenso, que pertence aos usuários.
  • Impossibilidade de censura — contas não podem ser bloqueadas, e as mensagens não podem ser excluídas.
  • Blockchain 2FA — uma alternativa ao horrível 2FA via SMS, que já causou muitos problemas.
  • A capacidade de acessar todos os seus diálogos de qualquer dispositivo a qualquer momento — é a oportunidade de não armazenar diálogos localmente.
  • Confirmação de entrega de mensagens. Não para o dispositivo do usuário, mas para a rede. Na verdade, isso confirma a capacidade do destinatário de ler sua mensagem. É um recurso útil para enviar notificações críticas.

Entre os benefícios do blockchain também está a integração estreita com criptomoedas como Ethereum, Dogecoin, Lisk, Dash, Bitcoin (este ainda está em andamento) e a possibilidade de enviar tokens em chats. Nós até fizemos uma exchange de cripto integrada.

E agora — como tudo isso funciona.

A mensagem é uma transação.

Todos já estão acostumados a que as transações na blockchain transferem tokens (moedas) de um usuário para outro. Assim como no Bitcoin. Nós criamos um tipo especial de transação para enviar mensagens.

Para enviar uma mensagem em um mensageiro na blockchain, é necessário passar por várias etapas:

  1. Criptografar o texto da mensagem
  2. Colocar o texto criptografado na transação
  3. Assinar a transação
  4. Enviar a transação para qualquer nó da rede
  5. O sistema distribuído de nós determina a 'veracidade' da mensagem
  6. Se tudo estiver OK — a transação com a mensagem é incluída no próximo bloco
  7. O destinatário extrai a transação com a mensagem e a descriptografa

As etapas 1–3 e 7 são executadas localmente no cliente, enquanto 5–6 são feitas nos nós da rede.

Criptografia da mensagem

A mensagem é criptografada com a chave privada do remetente e a chave pública do destinatário. A chave pública será obtida da rede, mas para isso a conta do destinatário deve estar inicializada, ou seja, deve ter pelo menos uma transação. Pode-se usar a requisição REST GET /api/accounts/getPublicKey?address={ADAMANT address}, e ao carregar os chats, as chaves públicas dos interlocutores já estarão disponíveis.

Como funciona um mensageiro descentralizado na blockchain

O mensageiro criptografa as mensagens usando o algoritmo curve25519xsalsa20poly1305 (NaCl Box). Como a conta contém chaves Ed25519, para formar o box, as chaves precisam ser transformadas previamente em Curve25519 Diffie-Hellman.

Aqui está um exemplo em 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)
  }
}

Formação da transação com a mensagem

A transação possui a seguinte estrutura geral:

{
  "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 a transação-mensagem, o valor mais importante é asset — nele deve-se colocar a mensagem em um objeto chat com a estrutura:

  • message — salvamos a mensagem criptografada
  • own_message — nonce
  • tipo — tipo de mensagem

As mensagens também são divididas em tipos. Na verdade, o parâmetro tipo indica como interpretar a mensagem. messagePode-se enviar apenas texto, ou pode-se enviar um objeto com informações interessantes dentro — por exemplo, assim o mensageiro faz transferências de criptomoedas em chats.

No final, formamos a transação:

{
  "transaction": {
    "type": 8,
    "amount": 0,
    "senderId": "U12499126640447739963",
    "senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
    "asset": {
      "chat": {
        "message": "cb682accceef92d7cddaaddb787d1184ab5428",
        "own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
        "type": 1
      }
    },
    "recipientId": "U15677078342684640219",
    "timestamp": 63228087,
    "signature": "aqui estará a assinatura"
  }
}

Assinatura da transação

Para que todos tenham certeza da autenticidade do remetente e do destinatário, do momento do envio e do conteúdo da mensagem, a transação é assinada. A assinatura digital permite verificar a autenticidade da transação com a chave pública — não sendo necessário a chave privada para isso.

E a própria assinatura é gerada com a chave privada:

Como funciona um mensageiro descentralizado na blockchain

Como mostrado no esquema, primeiro fazemos o hash da transação com SHA-256 e depois assinamos. Ed25519 EdDSA e obtemos a assinatura assinatura, e o identificador da transação é parte do hash SHA-256.

Exemplo de implementação:

1 — Formamos um bloco de dados, incluindo a mensagem

/**
 * 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 o SHA-256 do bloco de dados

/**
 * 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 — Assinamos a transação

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

/**
 * Cria uma assinatura com base em um hash e uma chave pública.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} assinatura
 *\/ 
adamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Envio da transação com a mensagem para um nó da rede

Como a rede é descentralizada, qualquer nó com API aberta serve. Fazemos uma solicitação POST para o endpoint api/transactions:

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

Em resposta, receberemos o ID da transação do tipo

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

Verificação da autenticidade da transação

Um sistema distribuído de nós baseado em consenso determina a 'autenticidade' da transação-mensagem. De quem e para quem, quando, se a mensagem não foi substituída por outra e se a hora do envio está correta. Essa é uma das grandes vantagens da blockchain — não há uma estrutura central que realize as verificações, e a sequência de mensagens e seu conteúdo não podem ser falsificados.

Primeiro, a autenticidade é verificada por um nó, que então dissemina a outros — se a maioria disser que está tudo certo, a transação será incluída no próximo bloco da cadeia — isso é o consenso.

Como funciona um mensageiro descentralizado na blockchain

Parte do código do nó que é responsável pelas verificações pode ser visualizada no GitHub — validator.js e verify.js. Ah, o nó está funcionando em Node.js.

Incluindo a transação com a mensagem no bloco

Se o consenso for alcançado, a transação com nossa mensagem será incluída no próximo bloco junto com outras transações válidas.

Os blocos têm uma sequência rigorosa, e cada bloco subsequente é formado com base nos hashes dos blocos anteriores.

Como funciona um mensageiro descentralizado na blockchain

A essência é que nossa mensagem também está incluída nessa sequência e não pode ser "reorganizada". Se várias mensagens entram em um bloco, sua ordem será determinada por timestamp mensagens.

Leitura de mensagens

O aplicativo de mensagens extrai transações da blockchain que foram enviadas ao destinatário. Para isso, criamos o endpoint api/chatrooms.

Todas as transações estão disponíveis para todos — é possível obter mensagens criptografadas. No entanto, apenas o destinatário poderá decifrá-las com sua chave privada e com a chave pública do remetente:

**
 * 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 mais?

Como as mensagens são entregues em cerca de 5 segundos — o tempo de geração de um novo bloco na rede — criamos uma conexão socket entre cliente e nó, e entre nós. Quando um nó recebe uma nova transação, ele verifica sua validade e a transmite para outros nós. A transação estará disponível para os clientes de mensagens antes mesmo de ocorrer o consenso e de ser incluída no bloco. Assim, entregaremos mensagens instantaneamente, como em aplicativos de mensagens convencionais.

Para armazenar a lista de contatos, criamos o KVS — Key-Value Storage — outro tipo de transação, nas quais asset não é criptografado o NaCl-box, mas sim NaCl-secretbox. Assim, o mensageiro armazena outros dados.

A transmissão de arquivos/imagens e chats em grupo ainda requer muito trabalho. Claro, no formato 'faça você mesmo' isso pode ser 'acoplado' rapidamente, mas queremos manter o mesmo nível de privacidade.

Sim, há muito a ser feito — idealmente, a verdadeira privacidade pressupõe que os usuários não se conectem a nós públicos, mas sim criem os seus próprios. Quantos porcento dos usuários você acha que faz isso? Correto, 0. Parcialmente, conseguimos resolver essa questão com a versão do mensageiro para o Tor.

Provamos que um mensageiro na blockchain pode existir. Anteriormente, houve apenas uma tentativa em 2012 — bitmessage, que falhou devido ao grande tempo de entrega das mensagens, carga no processador e à falta de aplicativos móveis.

O ceticismo está ligado ao fato de que os mensageiros baseados em blockchain estão à frente de seu tempo — as pessoas ainda não estão prontas para assumir a responsabilidade por suas próprias contas, a posse de informações pessoais ainda não é uma tendência, e as tecnologias ainda não permitem altas velocidades no blockchain. Em seguida, surgirão versões mais tecnológicas do nosso projeto. Espere e verá.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster