# Matrix 服务器完全去中心化方案 Matrix协议是一款开放的去中心化即时通讯协议,其类似于加强版的邮件系统。Matrix服务器相当于邮件服务器,用户可以在Matrix服务器上注册账户,账户信息以及聊天记录都存储在这台服务器。当需要与其他Matrix服务器上的用户聊天时,发送的消息通过自己的账户所在的Matrix服务器发送给对方的Matrix服务器,然后由对方的Matrix服务器推送到目标用户的Matrix客户端。 可以看到Matrix服务器存在中心化风险,如果Matrix服务器停止运营,那么上面的用户数据也会随之永久丢失。虽然每个人都可以搭建私人服务器来解决这个问题,但是这对于普通用户来说门槛太高。所以Matrix服务器有必要进行去中心化设计。 ## 方案描述 我们通过把用户数据保存区块链智能合约来实现Matrix服务器的去中心化。在没有Matrix服务器的情况下,用户依然可以随时获取自己的数据,也可以使用自己私钥的签名并发送一条链上交易来更新自己的数据,数据所有权完全属于用户。 如果一台Matrix服务器停止运营,用户可以切换到其它Matrix服务器,因为是Matrix服务器自动会从区块链上读取用户的个人数据,所以可以做到无缝切换。唯一遗憾的是用户会丢失在之前Matrix服务器上的 Session 和 Device 信息,需要重新进行登录授权。 方案涉及到链上智能合约和Matrix服务器两部分。 ### 智能合约 链上智能合约存储用户的个人信息,比如目前Matrix服务器已经存在的 `userapi_accounts`、`userapi_profiles` 表需要为其开发对应的智能合约保存数据。出于成本和性能考虑,数据量较大的内容,比如用户头像和 `userapi_account_datas` 表的内容,可以将其生成为独立文件保存至 ipfs 网络,然后将文件hash保存到智能合约。 ### Matrix服务器 Matrix服务器需要对**密码校验**、**数据存储**、**消息转发**三部分的规则进行改造。 1. **密码校验** 将抛弃以前注册和登录时使用用户名密码进行校验的方式,改用以太坊账户在本地钱包签名然后Matrix服务器验证签名的方式。 2. **数据存储** 用户个人数据(`account`、`profile` 和 `account_data` 等)不再存储到本地数据库,而是保存到区块链智能合约。为了加快查询速度Matrix服务器可以索引链上数据,然后再保存到本地数据库,本地数据库只能作为数据备份。 3. **消息路由** 当你的Matrix服务器停止运营后,其它Matrix服务器上的用户就不能向你发送消息。到目前为止虽然用户的个人数据得以保留,但是无法收到来自以前朋友的消息。Matrix协议的账户格式为:`@用户名:服务器Domain`,其它Matrix服务器上的用户会继续通过被关闭Matrix服务器向你发送消息!解决办法是引入虚拟服务器名,我们规定当用户的服务器Domain为 `matrixnet.eth` 时,不再直接向服务器发送消息,而是先从链上智能合约中查询用户当前设置的服务器Domain,然后向该URL发送消息。 ## 实现方案 ### 签名登录 我们采用 [EIP712](https://eips.ethereum.org/EIPS/eip-712) 作为以太坊账户签名验证的标准,EIP712可以提高链下消息签名在链上使用的可用性,从而节省GAS费用,还让用户知道他们在给什么数据进行签名。 用户在注册或者登录时,需要先通过以太坊钱包(比如MetaMask)签名以下结构体数据: ```js struct Message { string op; uint64 nonce; uint64 timestamp; } ``` * **op** 表示操作类型,可以设置为 `register` 或者 `login`。 * **nonce** 表示此签名的唯一标识,为了防止签名被盗取后多次使用,Matrix服务器应当对用户每次提供的 nonce 进行校验,禁止多次使用。 * **timestamp** 表示签名时的时间戳,如何签名中的时间戳与服务器时间相差大于阈值(比如 60 秒)则判定签名无效。 在任何需要 EIP712 签名的地方 eip712Domain 始终使用以下结构体数据: ```js struct { name: "Matrix", version: "1.0", chainId: 1, verifyingContract: "0x0000000000000000000000000000000000000000" } ``` 用户在客户端生成签名后请求注册/登录接口,此时需要将 `username` 字段设置为用户的以太坊账户地址,`password` 字段设置为消息的签名,然后Matrix服务器验证签名: 1. 消息的签名者与 `username` 是否相同 2. 签名消息中的 **op** 值是否合法 3. 签名消息中的 **nonce** 值用户是否只使用过一次 4. 签名消息中的 **timestamp** 值与服务器时间相差是否大于阈值 整体流程如下:  **参考资料** [ethers-eip712 - npm](https://www.npmjs.com/package/ethers-eip712) [Issuing and Verifying EIP-712 Challenges with Go](https://medium.com/alpineintel/issuing-and-verifying-eip-712-challenges-with-go-32635ca78aaf) ### 数据存储 去中心化版本的Matrix服务器会把用户的个人数据保存到区块链智能合约。但并不是所有数据都适合保存到智能合约,以用户信息表 `userapi_accounts` 为例,只需将 `account_type` 等于 `user` 的用户信息保存到链上,其它用户类型的信息依然存放在本地数据库。为了最小化地改动现有项目和兼顾数据查询效率,本地数据库仍然会保存链上的用户数据,但是业务流程会有所变动。 #### 业务流程 在这里以 Golang 版本的 HomeServer 软件 [dendrite](https://github.com/matrix-org/dendrite) 为例,在改造存储模块之前创建一个新的账户只需要向数据库插入一条记录即可:  去中心化版本的Matrix服务器引入了一个名为 **Indexer** 新模块。它用于索引链上智能合约的事件日志,然后保存到本地数据库。现在由于用户个人数据需要保存到智能合约,流程发生了一些变化,在 **storage** 模块的 `db.CreateAccount` 函数中进行判断:如果 `account_type` 为 `user` 则先发送一笔以太坊交易用于保存账户信息到智能合约,然后等待 **Indexer** 模块索引到交易产生的事件日志后再返回。如果不是则按照以前的流程执行,流程图如下所示:  在以前的流程里面最终需要执行 `accounts_table.InsertAccount` 函数向本地数据库插入记录,新的流程里面 **Indexer** 会索引链上智能合约的事件日志,如果发现链上创建了新的账户,就会调用 `accounts_table.InsertAccount` 函数向本地数据库插入记录,保证与链上的数据同步。这个方案有好处一个就是我们只需要修改插入和更新数据库部分的代码,而无需修改查询部分。 **Indexer** 模块会持续索引智能合约的事件日志,如果用户通过其它去中心化的Matrix服务器注册了账户,我们同样会将它保存到本地。这样就能够保证某个Matrix服务器停止运营之后,用户的账户依然可以在其它Matrix服务器上进行使用。 #### 文件存储 智能合约能够保存简单的用户数据,处理较大的数据的时候它就无能为力了,比如用户的头像文件。这时候我们需要引入 IPFS 或者 Arweave 来存储文件数,将文件从AWS S3转储到 IPFS 或者 Arweave 并不需要修改现在的业务流程。 由于IPFS是一个协议而不是一个区块链,无法保证永久性,所以需要使用第三方的服务商,比如 [Pinata](https://www.pinata.cloud/) 全球有数百个IPFS节点,能确保数据24小时在线,随时读取。只要保证数据不丢失,它们就能拿到Filecoin的奖励,从而激励长期保存工作。另外一个知名的服务商 [4everland](https://www.4everland.org/) 不仅可以将数据保存到IPFS网络,还可以备份数据到Arweave区块链,Arweave是一个永久存储区块链项目,只需一次付费就可以永久存储。 #### 代发交易 现在还有一个地方没有解释清楚,用户的个人数据所有权完全属于用户,那为什么Matrix服务器可以调用链上智能合约来创建和更新用户数据呢?这是因为链上智能合约在设计的时候每个操作都要提供两个方法,一个方法用户可以使用它的以太坊账户直接调用,另外一个方法用户使用私钥进行签名,然后任何人都可以用这个签名帮用户去调用它。以设置用户的头像为例,Profile 智能合约设置用户头像的操作需要提供以下两个方法: ```solidity function setAvatarURL(string calldata avatarURL); function setAvatarURLWithSig(DataTypes.SetAvatarURLWithSigData calldata vars); ``` Matrix 客户端在调用 `PUT /profile/{userID}/avatar_url` 接口更新用户头像之前需要先通过以太坊签名用私钥生成 `setAvatarURLWithSig` 所需的签名,然后将签名和接口所需操作一起传递给 Matrix 服务器。Matrix 服务器会用自己的私钥并附带用户提供的签名去调用智能合约 `setAvatarURLWithSig` 方法。  这个方案中Matrix服务器运营商将帮用户支付链上GAS费用,可以在一定程度上提升用户体验。另外我们还支持用户直接使用以太坊账户私钥签名交易去调用 Profile 合约的 `setAvatarURL` 方法来更新头像,这种情况下客户端无需调用 `PUT /profile/{userID}/avatar_url` 接口,Matrix服务器的 Indexer 模块会根据链上合约事件日志自动更新本地数据库中的用户头像。 ### 消息路由 Matrix协议似于邮件系统,Matrix服务器就相当于邮件服务器,用户可以在Matrix服务器上注册账户,账户信息以及聊天记录都存储在这台服务器上,帐号的格式也与邮箱地址很像:`@用户名:服务器Domain`。当需要与其他Matrix服务器上的用户聊天时,发送的消息通过自己的账户所在的Matrix服务器发送给对方的Matrix服务器,然后由对方的Matrix服务器推送到目标用户的Matrix客户端。Matrix协议还支持群聊(room),可以有多个地址。Matrix的群聊就是一个组播地址的列表,这个列表在群聊的每个地址对应的服务器中都存储一份。用户加入群聊后,用户所在服务器便加入到组播列表中。在群内发消息时,消息由自己的服务器转发给所有其他组播列表中的服务器。此外,当有新服务器加入到群聊中时,如果群设置允许的话,其他服务器可以将群消息同步给新服务器。  上图展示了位于 **matrix.eth** 服务器的用户 `a` 和位于 **matrix.org** 服务器的用户 `b` 互发消息的流程。我们可以看到如果 **matrix.eth** 服务器停止运营,那么其它服务器的用户就再也无法向用户 `a@matrix.eth` 发送消息了,因为其它服务器会把消息先发送给 **matrix.eth** 服务器,但是它已经不复存在了。 这个问题的一个解决方案是在去中心化的Matrix服务器引入一个虚拟服务器Domain。现在我们假设它是 `matrixnet.eth`,所有注册用户无论身处哪个服务器,用户名格式统一为:`{username}@matrixnet.eth`,在用户名中服务器Domain部分永远是 `matrixnet.eth`。用户真实的所处的服务器Domain则保存在链上智能合约,在需要获取用户所处服务器Domain的时候再去链上查询,当用户登录新的服务器时会更新链上用户所在服务器Domain状态。现在发送消息的流程变成了这样:  > 来自服务器 `server1.com` 的用户 `a@matrixnet.eth` 和来自 `server2.com` 的用户 `b@matrixnet.eth` 互相发送消息。 现在用户的账户迁移到了另外的一台Matrix服务器后,我们也能够通过智能合约找到它真实所处的服务器位置。只要能够找到它在哪里,用户以前的朋友们就可以继续向他发送消息。当用户以前使用的Matrix服务器停止运营后,他只需要登录到其它Matrix服务器,就会更新智能合约中保存的服务器Domain状态,以前的账户就可以继续在新的服务器上使用了。 #### 软件兼容性 我们通过引入虚拟服务器Domain解决了用户账户迁移后的问题,但是这并不是 Matrix Protocol 的一部分,在其它版本的 Home Server 实现(比如 [synapse](https://github.com/matrix-org/synapse) 和 [dendrite](https://github.com/matrix-org/dendrite))中并没有进行相关的处理,所以使用 synapse 和 dendrite 作为服务器的用户无法向我们发送消息。作为去中心化版本的开发商,我们真的需要部署一个去中心化版本并且 Domain 为 `matrixnet.eth` 的Matrix服务器,这样就可以帮助其它去中心化版本的Matrix服务器接受来自 synapse 和 dendrite 服务器的消息,其它版本的服务器向去中心化版本的服务器发送消息的流程如下:  在上图中用户 `a@matrix.org` 位于服务器 `matrix.org` 上,`matrix.org` 使用 [synapse](https://github.com/matrix-org/synapse) 作为服务器软件。现在用户 `a@matrix.org` 想给用户 `b@matrixnet.eth` 发送一条消息,用户 `b@matrixnet.eth` 位于服务器 `matrix.eth` 上,但是它使用的是去中心化版本的Matrix服务器软件。由于服务器 `matrix.org` 并不会从智能合约查询用户真实的服务器Domain,所以他以为用户 `b@matrixnet.eth` 位于服务器 `matrixnet.eth` 上。 接下来服务器会把来自用户 `a@matrix.org` 的消息转发至服务器 `matrixnet.eth`,再由服务器 `matrixnet.eth` 查询用户真实的服务器Domain,然后转发至真实的服务器 `matrix.eth`,最后服务器 `matrix.eth` 将消息推送给用户 `b@matrixnet.eth`。  我们可以看到使用 [synapse](https://github.com/matrix-org/synapse) 和 [dendrite](https://github.com/matrix-org/dendrite) 的Matrix服务器向去中心化版本的Matrix服务器(**DeMatrix**)发送消息时必须经过服务器 `matrixnet.eth` 进行中转,服务器 `matrixnet.eth` 使用的是去中心化版本的软件,拥有处理虚拟服务器Domain(`matrixnet.eth`)的能力。而去中心化版本的Matrix服务器向使用 [synapse](https://github.com/matrix-org/synapse) 和 [dendrite](https://github.com/matrix-org/dendrite) 的Matrix服务器发送消息时无需任何额外的处理。 现在我们已经实现了软件的兼容,但是由于 `matrixnet.eth` 服务器的存在在和使用 [synapse](https://github.com/matrix-org/synapse) 和 [dendrite](https://github.com/matrix-org/dendrite) 的Matrix服务器进行通信的时候还是出现了中心化的问题。`matrixnet.eth` 既是特殊的虚拟服务器Domain,也是一个真实存在的服务器。如果服务器 `matrixnet.eth` 出现故障,使用 [synapse](https://github.com/matrix-org/synapse) 和 [dendrite](https://github.com/matrix-org/dendrite) 服务器的用户就无法向使用去中心化版本服务器的用户发送消息了。 ### 好友和群组 TODO
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up