分散式信使如何在区块链上

在2017年初,我们通过讨论相对于经典P2P Messenger的优势开始在区块链上创建Messenger(名称和链接位于配置文件中)。

2.5年过去了,我们能够确认我们的概念:适用于iOS,Web PWA,Windows,GNU / Linux,Mac OS和Android的Messenger应用程序现已可用。

今天,我们将告诉您Messenger是如何安排在区块链上的,以及客户端应用程序如何与其API一起使用。


我们希望区块链解决经典P2P Messenger的安全和隐私问题:

  • 一键创建帐户-没有电话和电子邮件,无法访问地址簿和地理位置。
  • 对话者从不建立直接连接,所有通信都通过分布式的节点系统进行。 用户的IP地址不能互相访问。
  • 所有消息均已加密端到端曲线25519xsalsa20poly1305。 似乎您不会感到惊讶,但是我们拥有开源代码。
  • 不包括MITM攻击-每个消息都是一个事务,并由Ed25519 EdDSA签名。
  • 消息陷入其障碍。 块的顺序和timestamp无法确定,因此消息的顺序也无法确定。
  • “我没说这个”不适用于区块链上的消息。
  • 没有中央结构可以检查消息的“真实性”。 这是由基于共识的分布式节点系统完成的,它属于用户。
  • 无法进行审查-无法阻止帐户和删除邮件。
  • 2FA区块链是SMS破坏性的2FA的替代品,它破坏了很多健康状况。
  • 随时可以从任何设备获取所有对话框的能力是根本不将对话框存储在本地的能力。
  • 邮件传递确认。 不是用户设备,而是网络。 实际上,这是对收件人阅读消息能力的确认。 这是发送关键通知的有用功能。

在区块链包子中,还与以太坊,狗狗币,Lisk,达世币,比特币(仍在进行中)以及加密货币在聊天中发送令牌的能力紧密集成。 我们甚至制作了一个内置的加密货币交换器。

然后-一切如何进行。

消息是交易


每个人都已经习惯了区块链中的交易将令牌(硬币)从一个用户转移到另一个用户这一事实。 就像比特币。 我们创建了一种特殊的交易类型来发送消息。

要在区块链上的Messenger中发送消息,您需要经历几个阶段:

  1. 加密消息文本
  2. 将密文放入交易中
  3. 签署交易
  4. 将交易发送给任何主机
  5. 节点的分布式系统确定消息的“可靠性”
  6. 如果一切正常,则包含消息的事务将包含在下一个块中。
  7. 收件人检索消息事务并解密

阶段1-3和7在客户端本地执行,而阶段5-6在网络节点上执行。

讯息加密


邮件使用发件人的私钥和收件人的公钥加密。 我们将从网络上获取公钥,但是为此,收件人的帐户必须被初始化,也就是说,至少要进行一次交易。 您可以使用REST请求GET /api/accounts/getPublicKey?address={ADAMANT address} ,当您下载聊天时,对话者的公钥将已经可用。



Messenger会使用curve25519xsalsa20poly1305算法( NaCl Box )对消息进行加密。 由于该帐户包含Ed25519键,为了形成一个框,必须首先将这些键转换为Curve25519 Diffie-Hellman。

这是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) } } 

带有消息的交易的形成


事务具有以下一般结构:

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

对于事务消息, asset是最重要的-您需要将消息放入具有以下结构的chat对象中:

  • message -保存加密的消息
  • own_message随机数
  • type -消息类型

消息也分为类型。 本质上, type参数告诉您如何理解message 。 您可以只发送文本,也可以发送内部具有有趣兴趣的对象-例如,这就是Messenger在聊天室中进行加密货币传输的方式。

结果,我们形成了交易:

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

交易签名


为了使每个人都能够确定发送方和接收方的真实性,在发送时和消息内容中,都对交易进行了签名。 数字签名使您可以使用公共密钥来验证交易的真实性-不需要私有密钥。

但是签名本身只是由私钥执行的:



从该图中可以看出,我们首先使用SHA-256对事务进行哈希处理,然后对Ed25519 EdDSA进行签名并获得signature ,并且事务标识符是SHA-256哈希的一部分。

实施示例:


1-我们形成一个数据块,包括一条消息

 /** * 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-我们考虑数据块中的SHA-256

 /** * 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-我们签署交易

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

将带有消息的事务发送给主机


由于网络是分散式的,因此任何具有开放API的节点都可以。 我们向api/transactions端点发出POST请求:

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

作为回应,我们得到一个类型的交易ID

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

交易验证


基于分布式共识的节点系统确定消息事务的“可靠性”。 从谁和谁来,何时,是否用另一条消息替换了该消息以及是否正确指示了发送时间。 这是区块链的一个非常重要的优势-没有负责检查的中央结构,并且消息序列及其内容不能伪造。

首先,一个节点检查可靠性,然后将其发送给其他节点-如果大多数人都说一切都井井有条,则该交易将包含在链的下一个区块中-这是共识。



可以在GitHub上查看负责验证的主机代码部分-validateator.jsverify.js 。 是的,该节点在Node.js上运行。

将交易包含在消息中


如果达成共识,则带有我们消息的交易将与其他可靠交易一起进入下一阶段。

块具有严格的顺序,并且每个后续块都是基于先前块的哈希值形成的。



底线是我们的消息也包含在此序列中,并且不能“重新排列”。 如果多个消息落入该块,则它们的顺序将由消息的timestamp确定。

阅读讯息


Messenger应用程序从区块链中检索发送到收件人的交易。 为此,我们制作了api/chatrooms端点。

所有交易对所有人都适用-您可以接收加密的消息。 但是,只有收件人可以使用其私钥和发送者的公钥进行解密:

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

还有什么


由于消息以这种方式传递大约5秒钟(这是出现一个新的网络块的时间),因此我们想到了套接字连接客户端节点和节点到节点。 当节点接收到新的事务时,它将检查其有效性并将其转移到其他节点。 即使在达成共识并将其包含在区块中之前,该交易也可供信使客户使用。 因此,我们将立即发送消息,以及通常的使者。

为了存储地址簿,我们使KVS-键值存储是另一种类型的事务,其中不使用NaCl-box加密asset ,而是使用NaCl-secretbox加密asset 。 因此,Messenger会存储其他数据。

文件/图像传输和群聊仍然需要大量工作。 当然,在tyap-bloop格式中,可以快速固定该格式,但是我们希望保持相同级别的隐私。

是的,仍然有工作要做-理想情况下,真正的隐私意味着用户不会连接到公共网络节点,而是会自己建立。 您如何看待,有百分之几的用户这样做? 是的,是0。部分地,我们设法使用Tor版本的Messenger来解决此问题。

我们证明了区块链上的一个使者可以存在。 以前,在2012年只有一种尝试-bitmessage ,由于消息传递时间长,CPU负载大和缺少移动应用程序而失败。

但是怀疑与以下事实有关:区块链上的信使提前到达-人们还没有准备好对自己的账户负责,个人信息的所有权还不是趋势,并且技术不允许确保区块链的高速运行。 以下将出现我们项目的更多技术类似物。 您会看到的。

Source: https://habr.com/ru/post/zh-CN467751/


All Articles