Como um mensageiro descentralizado na blockchain

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

2.5 anos se passaram e pudemos confirmar nosso conceito: aplicativos de mensagens instantâneas para iOS, Web PWA, Windows, GNU / Linux, Mac OS e Android já estão disponíveis.

Hoje, mostraremos como o messenger está organizado na blockchain e como os aplicativos clientes podem funcionar com sua API.


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

  • Um clique para criar uma conta - sem telefones e e-mails, sem acesso a catálogos de endereços e geolocalizações.
  • Os interlocutores nunca estabelecem conexões diretas, toda a comunicação passa por um sistema distribuído de nós. Os endereços IP dos usuários não são acessíveis um ao outro.
  • Todas as mensagens são criptografadas curva de ponta a ponta25519xsalsa20poly1305. Parece que você não surpreenderá ninguém, mas temos código-fonte aberto.
  • O ataque MITM é excluído - cada mensagem é uma transação e é assinada pelo Ed25519 EdDSA.
  • A mensagem cai em seu bloco. A sequência e o timestamp e timestamp blocos não podem ser fixados e, portanto, a ordem das mensagens.
  • "Eu não disse isso" não funcionará com mensagens no blockchain.
  • Não existe uma estrutura central que verifique a "autenticidade" de uma mensagem. Isso é feito por um sistema de nós distribuídos com base em consenso e pertence aos usuários.
  • Impossibilidade de censura - as contas não podem ser bloqueadas e as mensagens excluídas.
  • O blockchain 2FA é uma alternativa ao 2FA infernal por SMS, que prejudicou bastante a saúde.
  • A capacidade de obter todas as suas caixas de diálogo de qualquer dispositivo a qualquer momento é a capacidade de não armazenar caixas de diálogo localmente.
  • Confirmação de Entrega de Mensagens. Não para o dispositivo do usuário, mas para a rede. De fato, esta é uma confirmação da capacidade do destinatário de ler sua mensagem. Esse é um recurso útil para o envio de notificações críticas.

Dos pães blockchain, também há uma estreita integração com as criptomoedas Ethereum, Dogecoin, Lisk, Dash, Bitcoin (isso ainda está em andamento) e a capacidade de enviar tokens nos chats. Até fizemos um trocador de criptografia embutido.

E então - como tudo funciona.

A mensagem é uma transação


Todo mundo já está acostumado ao fato de que as transações nos blockchain transferem tokens (moedas) de um usuário para outro. Como bitcoin. Criamos um tipo especial de transação para o envio de mensagens.

Para enviar uma mensagem no messenger na blockchain, você precisa passar por vários estágios:

  1. Criptografar o texto da mensagem
  2. Colocar texto cifrado em transação
  3. Assinar transação
  4. Envie uma transação para qualquer host
  5. Um sistema distribuído de nós determina a "confiabilidade" de uma mensagem
  6. Se tudo estiver correto, a transação com a mensagem será incluída no próximo bloco.
  7. O destinatário recupera a transação da mensagem e descriptografa

Os estágios 1-3 e 7 são executados localmente no cliente e 5-6 nos nós da rede.

Criptografia de mensagens


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



O messenger criptografa as mensagens com o algoritmo curve25519xsalsa20poly1305 ( NaCl Box ). Como a conta contém chaves Ed25519, para formar uma caixa, as chaves devem primeiro ser convertidas 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 de uma transação com uma mensagem


Uma 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 asset é o mais importante - você precisa colocar a mensagem no objeto de chat - chat com a estrutura:

  • message - salva a mensagem criptografada
  • own_message - nonce
  • type - tipo de mensagem

As mensagens também são divididas em tipos. Essencialmente, o parâmetro type diz como entender a message . Você pode enviar apenas texto ou enviar um objeto com interesses interessantes - por exemplo, é assim que o messenger faz transferências de criptomoedas nas salas de bate-papo.

Como resultado, 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": "  " } } 

Assinatura de transação


Para que todos tenham certeza da autenticidade do remetente e do destinatário, no momento do envio e do conteúdo da mensagem, a transação é assinada. Uma assinatura digital permite verificar a autenticidade de uma transação usando uma chave pública - uma chave privada não é necessária para isso.

Mas a assinatura em si é apenas executada pela chave privada:



Pode ser visto no diagrama que primeiro fizemos o hash da transação com o SHA-256 e, em seguida, assinamos o Ed25519 EdDSA e obtemos a assinatura da signature , e o identificador da transação faz parte do hash do SHA-256.

Exemplo de implementação:


1 - Formamos um bloco de dados, incluindo uma 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 breakdefault: 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 - Consideramos 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') } /** * 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')) } 

Enviando uma transação com uma mensagem para um host


Como a rede é descentralizada, qualquer um dos nós com uma API aberta funcionará. Fazemos uma solicitação POST para o ponto de extremidade da api/transactions :

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

Em resposta, obtemos um ID de transação do tipo

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

Validação de transação


Um sistema de nós distribuído com base em consenso determina a "confiabilidade" de uma transação de mensagem. De quem e para quem, quando, se a mensagem foi substituída por outra e se a hora do envio foi indicada corretamente. Essa é uma vantagem muito importante da blockchain - não existe uma estrutura central responsável pelas verificações, e a sequência de mensagens e seu conteúdo não pode ser falsificada.

Primeiro, um nó verifica a confiabilidade e depois a envia para outros - se a maioria disser que está tudo em ordem, a transação será incluída no próximo bloco da cadeia - isso é consenso.



A parte do código do host responsável pela validação pode ser visualizada no GitHub - validator.js e confirm.js . Sim, o nó é executado no Node.js.

Incluir transação com mensagem no bloco


Se for alcançado um consenso, a transação com a nossa mensagem cairá no próximo bloco, juntamente com outras transações confiáveis.

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



A conclusão é que nossa mensagem também está incluída nesta sequência e não pode ser "reorganizada". Se várias mensagens caírem no bloco, sua ordem será determinada pelo timestamp mensagens.

Lendo mensagens


O aplicativo messenger recupera transações do blockchain que são enviadas ao destinatário. Para fazer isso, criamos o ponto de extremidade da api/chatrooms .

Todas as transações estão disponíveis para todos - você pode receber mensagens criptografadas. Mas apenas o destinatário pode descriptografar com sua chave privada e 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) : '' } 

O que mais?


Como as mensagens são entregues dessa maneira por cerca de 5 segundos - esse é o momento em que um novo bloco de rede apareceu -, criamos uma conexão de soquete cliente-nó e nó-a-nó. Quando um nó recebe uma nova transação, ele verifica sua validade e a transfere para outros nós. A transação está disponível para clientes mensageiros mesmo antes do início do consenso e da inclusão no bloco. Então, entregaremos mensagens instantaneamente, bem como os mensageiros habituais.

Para armazenar o catálogo de endereços, criamos o KVS - Key-Value Storage é outro tipo de transação na qual um asset é criptografado não com o NaCl-box, mas com o NaCl-secretbox . Então o mensageiro armazena outros dados.

A transferência de arquivos / imagens e bate-papos em grupo ainda exigem muito trabalho. Obviamente, no formato tyap-bloop, isso pode ser fixado rapidamente, mas queremos manter o mesmo nível de privacidade.

Sim, ainda há trabalho a ser feito - idealmente, a privacidade real implica que os usuários não se conectam aos nós da rede pública, mas aumentam os seus. O que você acha, quantos por cento dos usuários fazem isso? É isso mesmo, 0. Parcialmente, conseguimos resolver esse problema com a versão Tor do messenger.

Provamos que um messenger no blockchain pode existir. Anteriormente, havia apenas uma tentativa em 2012 - a mensagem de bit , que falhou devido ao longo tempo de entrega da mensagem, carga da CPU e falta de aplicativos móveis.

Mas o ceticismo está ligado ao fato de que os mensageiros da blockchain estão adiantados - as pessoas não estão prontas para assumir a responsabilidade por suas próprias contas, a propriedade de informações pessoais ainda não é uma tendência e a tecnologia não permite garantir altas velocidades na blockchain. A seguir, aparecerão mais análogos tecnológicos do nosso projeto. Você verá.

Source: https://habr.com/ru/post/pt467751/


All Articles