精子生于 1995 年,英文 ID jysperm.
零毫秒的图景一下子清晰起来了
去中心化的零毫秒计划了很久,一直都没能开始,原因很简单,就是我无法想象这样一个项目应该如何设计,都有哪些部分,从哪开始。
即使这两年来我学习了不少有关公钥加密和证书体系,Bitcoin 的实现,一些 DHT 网络的实现等,但依旧如此。
这段时间有很多目标类似的项目出现,我所知道的就有 BitMessage, BitTorrent Chat, Tox.
所以我以为这个项目就要这么坑掉了。
但前一阵,我一直想着重写 ZeroMS-1x, 即两年前我初学 Qt 的时候,写的零毫秒的第一个版本,一个十分简易的中心化,C/S 结构的聊天工具。
重写的目的也很简单,只是希望当初花了好大功夫写的东西不至于不能运行——虽然重写的时间应该不会小于当初花费的时间。
于是我开始设想如何设计这个重写版本。
首先不能再使用之前那丑陋的通讯协议载体,转而使用 JSON.
然后就是之前那蛋疼的帐号机制。
之前的帐号机制是使用 PHPWind 论坛(后来是 esoTalk)系统的帐号系统,服务器会请求论坛上的一个 PHP API 来验证登录信息。
我决定使用公钥加密(RSA), 的密钥对代替帐号系统。
一对公私玥就是一个帐号,公钥是帐号的唯一 ID, 私钥是持有帐号的凭证。
登录时,客户端用私钥为登录信息签名,同时提供一个短的,不唯一,可变的昵称作为友好的显示名。
再进一步,可以让发信人对所有发出的消息进行签名,以认证身份。
再进一步,可以让发信人对所有签名过的消息,用收信人的公钥进行加密,使只有拥有私钥的收信人才能解密。
至此,我们惊奇地发现,虽然整体仍是 C/S 结构的网络,但是我们似乎已经剥夺了服务器的大部分权力——服务器无法查看消息的内容,也无法篡改或伪造消息。
于是,服务器似乎变成了一个非必须的部分,因为作为服务器,不需要什么资格,也没有什么权力,任何人都可以当服务器!
甚至可以让多个服务器接力地完成一个消息的送达过程。只需要送达就可以了!无论中间是谁来传递的,也无论中间有多少人经手,因为它们看不了消息也改不了消息,就算你写在纸上飞鸽传书也没有什么不可以。
这时的服务器已经不能叫做服务器了,应该叫网关或者路由,就像 IP 中的网关一样,工作是将 IP 数据包送达指定的地址。IP 网络的网关各自维护了自己的路由表,同时基于 IP 地址的 IP 网络也是一个结构化的网络,所以这很简单。
而在零毫秒的网关之间,可以维护一个分布式散列表(DHT), 如类似 Kademlia 协议的 DHT, 储存网络上每个用户(公钥)和所对应的地址。
这样一来,原来我想要的去中心化即时通讯就是这么简单!之前一直把它想得过于复杂了,原来就是这么简单的一个构造而已!
既然图景已经清晰,我们还可以讨论一些更为细节的话题。
首先是公钥交换,通过上面的设计,要与一个人通讯,必须知道他的公钥,当然,获取公钥的过程很简单,问题是如何保证这个过程的安全性呢?如果密钥在通信的途中被替换了怎么办?这在 HTTP 环境下很容易发生。
有人提出应成立一个证书颁发机构(CA), 对用户的公钥进行签名,但这似乎有悖于去中心化的精神。
我认为公钥交换应该由用户自行解决,用户可以自行选择渠道,如 HTTP, HTTPS, 其他 IM 如 QQ, 当面交换纸质(二维码)公钥。而事实上也有提供公钥交换服务的网站,如 pgp.mit.edu (我不得不吐槽一下这个网站居然只有 HTTP 版本), 这些望着本来是为了交换 PGP 公钥而设计的,不过对零毫秒也是适用的。
因为用户可以自行选择渠道,用户的选择越多样,「信任链」的构成就越分散,攻击者发起攻击的成本就越高,整个系统就越安全。
之前我们讨论过,网关无法阅读或修改流经它的消息,但是网关可以选择丢弃消息,不予转发,那么如何应对这种消息丢失的情况呢?
事实上 IP 的网关也有类似的特点,即它可以随意丢弃消息,IP 对此的解决方案就是不予考虑,将这个工作交由上层协议来实现,比如 TCP.
TCP 会在通讯的双方,也就是两个端点来进行一些操作,而中间的 IP 网关不必考虑,甚至不必知道这是一个承载 TCP 协议的包。
「在端点实现功能」也是 TCP/IP 网络体系的一大特点。
由此,零毫秒中的两个客户端之间,应该自行协商,防止消息丢失。
最简单的办法就是在每一条消息中,嵌入上一条消息的散列值(Hash), 当中间的某个消息丢失时,双方可以察觉到,自行协商,对丢失的消息进行重传。
这样一来,客户端需要自行维护很多状态,例如对于每个联系人的上一条消息,这导致用户在更换设备时需要一并携带这些信息,否则就会导致通讯不正常,这是目前很难解决的问题,最理想的就是使用同步盘服务(可以是自建的)来同步这些数据。
另一方面是接收离线消息,客户端可以指定一个长期在线的网关作为离线代理,由这个网关来代收离线消息,上线后再从这个网关抓取离线消息,这符合在端点实现功能的原则。
这个网关可以是用户自建的,也可以是公共的「离线代理网关」。
最后要讨论的一个话题就是组群。
建立一个组群就是生成一个新的密钥对,公钥即为该组群的ID, 私钥由管理员掌握,用于签发新成员加入和移除现有成员的通知。
然后组群的成员根据管理员签发的公告,计算出目前的成员列表,逐个发送消息。
这个实现似乎很不完善,既无法阻止成员把消息发到非组群成员那,也无法阻止成员忽略组群中的一些成员,全靠成员的自觉。
而且如果有其他 10 个成员,那么每条消息就需要发送 10 遍,因为要保证网关不能阅读消息的内容,每一条消息都需要用不同的,收信人的公钥来加密。