Comment fonctionne un messager décentralisé sur la blockchain

Début 2017, nous avons commencé à créer un messager sur la blockchain [le nom et le lien sont dans le profil] en discutant des avantages par rapport aux messagers P2P classiques.

2.5 ans se sont écoulés et nous avons pu confirmer notre concept: des applications messenger pour iOS, Web PWA, Windows, GNU / Linux, Mac OS et Android sont désormais disponibles.

Aujourd'hui, nous vous expliquerons comment le messager est organisé sur la blockchain et comment les applications clientes peuvent fonctionner avec son API.


Nous voulions que la blockchain résolve les problèmes de sécurité et de confidentialité des messagers P2P classiques:

  • Un clic pour créer un compte - pas de téléphones et de courriels, pas d'accès aux carnets d'adresses et aux géolocalisations.
  • Les interlocuteurs n'établissent jamais de connexions directes, toutes les communications passent par un système distribué de nœuds. Les adresses IP des utilisateurs ne sont pas accessibles les unes aux autres.
  • Tous les messages sont cryptés End-to-End curve25519xsalsa20poly1305. Il semble que vous ne surprendrez personne, mais nous avons du code open source.
  • L'attaque MITM est exclue - chaque message est une transaction et est signé par Ed25519 EdDSA.
  • Le message tombe dans son bloc. La séquence et l' timestamp blocs ne peuvent pas être fixés, et donc l'ordre des messages.
  • "Je n'ai pas dit cela" ne fonctionnera pas avec les messages sur la blockchain.
  • Il n'y a pas de structure centrale qui vérifie «l'authenticité» d'un message. Cela se fait par un système de nœuds distribués consensuel et appartient aux utilisateurs.
  • Impossibilité de censure - les comptes ne peuvent pas être bloqués et les messages supprimés.
  • La blockchain 2FA est une alternative à la 2FA infernale par SMS, qui a brisé beaucoup de santé.
  • La possibilité d'obtenir à tout moment toutes vos boîtes de dialogue depuis n'importe quel appareil est la possibilité de ne pas stocker de boîtes de dialogue localement.
  • Confirmation de remise du message. Pas à l'appareil de l'utilisateur, mais au réseau. En fait, c'est une confirmation de la capacité du destinataire à lire votre message. Il s'agit d'une fonctionnalité utile pour l'envoi de notifications critiques.

Parmi les brioches de la blockchain, il existe également une intégration étroite avec les crypto-monnaies Ethereum, Dogecoin, Lisk, Dash, Bitcoin (cela est toujours en cours) et la possibilité d'envoyer des jetons dans les chats. Nous avons même fabriqué un crypto-échangeur intégré.

Et puis - comment tout cela fonctionne.

Le message est une transaction


Tout le monde est déjà habitué au fait que les transactions dans la blockchain transfèrent des jetons (pièces) d'un utilisateur à un autre. Comme le bitcoin. Nous avons créé un type spécial de transaction pour l'envoi de messages.

Pour envoyer un message dans le messager sur la blockchain, vous devez passer par plusieurs étapes:

  1. Crypter le texte du message
  2. Mettre un texte chiffré en transaction
  3. Signez la transaction
  4. Envoyer une transaction à n'importe quel hôte
  5. Un système distribué de nœuds détermine la «fiabilité» d'un message
  6. Si tout va bien, la transaction avec le message est incluse dans le bloc suivant.
  7. Le destinataire récupère la transaction de message et déchiffre

Les étapes 1-3 et 7 sont exécutées localement sur le client et 5-6 sur les nœuds du réseau.

Cryptage des messages


Le message est crypté avec la clé privée de l'expéditeur et la clé publique du destinataire. Nous prendrons la clé publique du réseau, mais pour cela, le compte du destinataire doit être initialisé, c'est-à-dire avoir au moins une transaction. Vous pouvez utiliser la demande REST GET /api/accounts/getPublicKey?address={ADAMANT address} , et lorsque vous téléchargez des chats, les clés publiques des interlocuteurs seront déjà disponibles.



Le messager crypte les messages avec l'algorithme curve25519xsalsa20poly1305 ( NaCl Box ). Étant donné que le compte contient des clés Ed25519, afin de former une boîte, les clés doivent d'abord être converties en Curve25519 Diffie-Hellman.

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

Formation d'une transaction avec un message


Une transaction a la structure générale suivante:

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

Pour le message de transaction, l' asset est le plus important - vous devez y placer le message dans l'objet de chat avec la structure:

  • message - enregistrez le message crypté
  • own_message - nonce
  • type - type de message

Les messages sont également divisés en types. Essentiellement, le paramètre type indique comment comprendre le message . Vous pouvez envoyer uniquement du texte, ou vous pouvez envoyer un objet avec des intérêts à l'intérieur - par exemple, c'est ainsi que le messager effectue des transferts de crypto-monnaies dans les salles de chat.

En conséquence, nous formons la transaction:

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

Signature de la transaction


Afin que chacun soit sûr de l'authenticité de l'expéditeur et du destinataire, au moment de l'envoi et du contenu du message, la transaction est signée. Une signature numérique vous permet de vérifier l'authenticité d'une transaction à l'aide d'une clé publique - une clé privée n'est pas nécessaire pour cela.

Mais la signature elle-même n'est effectuée que par la clé privée:



Le diagramme montre que nous avons d'abord haché la transaction avec SHA-256, puis signé Ed25519 EdDSA et obtenu la signature de signature , et l'identifiant de transaction fait partie du hachage SHA-256.

Exemple d'implémentation:


1 - Nous formons un bloc de données, comprenant un message

 /** * 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 - Nous considérons SHA-256 du bloc de données

 /** * 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 - Nous signons la transaction

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

Envoi d'une transaction avec un message à un hôte


Puisque le réseau est décentralisé, n'importe lequel des nœuds avec une API ouverte fera l'affaire. Nous faisons une demande POST pour le point final api/transactions :

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

En réponse, nous obtenons un ID de transaction de type

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

Validation de transaction


Un système distribué de nœuds basé sur le consensus détermine la «fiabilité» d'une transaction de message. De qui et à qui, quand, si le message a été remplacé par un autre et si l'heure d'envoi a été indiquée correctement. C'est un avantage très important de la blockchain - il n'y a pas de structure centrale responsable des contrôles, et la séquence des messages et leur contenu ne peuvent pas être falsifiés.

D'abord, un nœud vérifie la fiabilité, puis l'envoie aux autres - si la plupart disent que tout est en ordre, la transaction sera incluse dans le bloc suivant de la chaîne - c'est un consensus.



La partie du code hôte responsable de la validation peut être consultée sur GitHub - validator.js et verify.js . Oui, le nœud fonctionne sur Node.js.

Inclure la transaction avec le message dans le bloc


Si un consensus est atteint, la transaction avec notre message tombera dans le bloc suivant, avec d'autres transactions fiables.

Les blocs ont une séquence stricte et chaque bloc suivant est formé sur la base des hachages des blocs précédents.



L'essentiel est que notre message est également inclus dans cette séquence et ne peut pas être «réorganisé». Si plusieurs messages tombent dans le bloc, leur ordre sera déterminé par l' timestamp messages.

Lire des messages


L'application de messagerie récupère les transactions de la blockchain qui sont envoyées au destinataire. Pour ce faire, nous avons créé le point de terminaison api/chatrooms .

Toutes les transactions sont accessibles à tous - vous pouvez recevoir des messages cryptés. Mais seul le destinataire peut décrypter avec sa clé privée et la clé publique de l'expéditeur:

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

Quoi d'autre?


Puisque les messages sont livrés de cette manière pendant environ 5 secondes - c'est le moment où un nouveau bloc réseau est apparu - nous avons proposé une connexion de socket client-nœud et nœud à nœud. Lorsqu'un nœud reçoit une nouvelle transaction, il vérifie sa validité et la transfère vers d'autres nœuds. La transaction est disponible pour les clients de messagerie avant même le début du consensus et l'inclusion dans le bloc. Nous allons donc livrer des messages instantanément, ainsi que les messagers habituels.

Pour stocker le carnet d'adresses, nous avons fait de KVS - Key-Value Storage est un autre type de transaction dans lequel un asset est crypté non pas avec NaCl-box, mais avec NaCl-secretbox . Le messager stocke donc d'autres données.

Le transfert de fichiers / images et les discussions de groupe nécessitent encore beaucoup de travail. Bien sûr, dans le format tyap-bloop, cela peut être fixé rapidement, mais nous voulons maintenir le même niveau de confidentialité.

Oui, il y a encore du travail à faire - idéalement, une réelle confidentialité implique que les utilisateurs ne se connecteront pas aux nœuds du réseau public, mais augmenteront les leurs. Que pensez-vous, combien de pour cent des utilisateurs font cela? C'est vrai, 0. En partie, nous avons réussi à résoudre ce problème avec la version Tor du messager.

Nous avons prouvé qu'un messager sur la blockchain peut exister. Auparavant, il n'y avait eu qu'une seule tentative en 2012 - bitmessage , qui avait échoué en raison du long délai de livraison des messages, de la charge du processeur et du manque d'applications mobiles.

Mais le scepticisme est lié au fait que les messagers sur la blockchain sont en avance - les gens ne sont pas prêts à assumer la responsabilité de leur compte eux-mêmes, la propriété des informations personnelles n'est pas encore une tendance et la technologie ne permet pas d'assurer des vitesses élevées sur la blockchain. Ci-dessous apparaîtront plus d'analogues technologiques de notre projet. Tu verras.

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


All Articles