2017 年初,我们开始在区块链上创建一个信使(名称和链接在个人资料中),讨论相对于经典 P2P 信使的优势。
已通过 2.5
今年,我们确认了我们的概念:即时通讯应用程序现在可用于 iOS、Web PWA、Windows、GNU/Linux、Mac OS 和 Android。
今天我们将告诉您区块链信使如何工作以及客户端应用程序如何与其 API 配合使用。
我们希望区块链能够解决经典 P2P 通讯工具的安全和隐私问题:
- 一键创建帐户 - 无需电话或电子邮件,无法访问地址簿或地理位置。
- 对话者从不建立直接连接;所有通信都是通过分布式节点系统进行的。 用户的IP地址不能互相访问。
- 所有消息均经过端到端 curve25519xsalsa20poly1305 加密。 看来这不会让任何人感到惊讶,但我们的源代码是开放的。
- 排除 MITM 攻击 - 每条消息都是一个交易,并由 Ed25519 EdDSA 签名。
- 消息最终出现在它自己的块中。 一致性和
timestamp
您无法修复块,因此也无法修复消息的顺序。 - “我没有这么说”不适用于区块链上的消息。
- 没有中央结构来检查消息的“真实性”。 这是由基于共识的分布式节点系统完成的,并且由用户拥有。
- 无法进行审查 - 帐户无法被阻止,消息也无法被删除。
- 区块链 2FA 是地狱般的短信 2FA 的替代方案,
毁掉了很多健康。 - 能够随时从任何设备获取所有对话意味着您根本不必在本地存储对话。
- 确认消息传送。 不是到用户的设备,而是到网络。 本质上,这是对收件人能够阅读您的邮件的确认。 这是发送重要通知的有用功能。
区块链的好处还包括与加密货币以太坊、狗狗币、Lisk、达世币、比特币(这一项目仍在进行中)的紧密集成以及在聊天中发送代币的能力。 我们甚至制作了一个内置的加密货币交换器。
然后——这一切是如何运作的。
一条消息就是一次交易
每个人都已经习惯了区块链中的交易将代币(硬币)从一个用户转移到另一个用户的事实。 就像比特币一样。 我们创建了一种特殊类型的交易来传输消息。
要在区块链上的Messenger中发送消息,需要经过几个步骤:
- 加密消息文本
- 将密文放入交易中
- 签署交易
- 向任意网络节点发送交易
- 分布式节点系统确定消息的“真实性”
- 如果一切正常,带有消息的交易将包含在下一个块中
- 接收者检索消息交易并解密
步骤1-3和7在客户端本地执行,步骤5-6在主机上执行。
消息加密
该消息使用发送者的私钥和接收者的公钥进行加密。 我们将从网络中获取公钥,但为此,接收者的帐户必须被初始化,即至少有一笔交易。 您可以使用 REST 请求 GET /api/accounts/getPublicKey?address={ADAMANT address}
,并且在加载聊天时,对话者的公钥将已经可用。
信使使用 curve25519xsalsa20poly1305 算法加密消息(
这是一个 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
。 您可以仅发送文本,也可以发送包含有趣内容的对象 - 例如,这就是信使在聊天中进行加密货币传输的方式。
结果,我们创建了一个交易:
{
"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 对交易进行哈希处理,然后对其进行签名 signature
,交易 ID 是 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
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 - 从数据块中计算 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 的节点都可以。 向端点发出 POST 请求 api/transactions
:
curl 'api/transactions' -X POST
-d 'TX_DATA'
作为响应,我们将收到以下类型的交易 ID
{
"success": true,
"nodeTimestamp": 63228852,
"transactionId": "6146865104403680934"
}
交易验证
基于共识的分布式节点系统确定交易消息的“真实性”。 消息来自谁、发送给谁、何时、消息是否被其他消息替换以及发送时间是否指示正确。 这是区块链非常重要的优势——没有负责验证的中心结构,消息的顺序及其内容无法伪造。
首先,一个节点检查准确性,然后将其发送给其他节点 - 如果大多数人认为一切正常,则该交易将包含在链的下一个区块中 - 这就是共识。
负责检查的节点代码部分可以在GitHub上查看——
在区块中包含带有消息的交易
如果达成共识,带有我们消息的交易将与其他有效交易一起包含在下一个区块中。
块具有严格的顺序,每个后续块都是基于先前块的哈希值形成的。
关键是我们的消息也包含在这个序列中并且不能被“重新排列”。 如果多条消息落入一个块,它们的顺序将由 timestamp
消息。
阅读信息
消息应用程序从区块链中检索发送给收件人的交易。 为此我们做了一个端点 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 - 键值存储 - 这是另一种类型的事务,其中 asset
加密的不是 NaCl-box,而是
文件/图像传输和群聊仍然需要大量工作。 当然,在错误和错误的格式中,这可能会很快“搞砸”,但我们希望保持相同水平的隐私。
是的,仍有工作要做——理想情况下,真正的隐私假设用户不会连接到公共网络节点,而是会提升自己的节点。 您认为有多少比例的用户会这样做? 没错,0。我们能够使用 Tor 版本的信使部分解决这个问题。
我们已经证明区块链上的信使是可以存在的。 此前,2012年只有一次尝试——
怀疑是由于区块链上的信使超前了——人们还没有准备好对自己的账户负责,拥有个人信息还不是一种趋势,而且技术不允许区块链上高速。 接下来将出现更多与我们项目类似的技术。 你会看到的。
来源: habr.com