Whisper - DApps 相互通信的通信协议, 以太坊 web3 技术栈的基础层服务
概述
whisper是完全基于ID的消息系统,它的设计目的是形成一套p2p节点间的异步广播系统。whisper网络上的消息是加密传送的,完全可以暴露在公网进行传输;此外,为了防范DDos攻击,whisper使用了proof-of-work(PoW)工作量证明提高消息发送门槛。
whisper基础构件
whisper协议对上层暴露出一套类似于订阅-发布的API模型,节点可以申请自己感兴趣的topic,那么就只会接收到这些topic的消息,无关主题的消息将被丢弃。在这套体系内,有几个基础构件需要说明下:
Envelope信封
envelope即信封是whisper网络节点传输数据的基本形式。信封包含了加密的数据体和明文的元数据,元数据主要用于基本的消息校验和消息体的解密。
信封是以RLP编码的格式传输:Version:最多4字节(目前仅使用了1字节),如果信封的Version比本节点当前值高,将无法解密,仅做转发
Expiry:4字节(unix时间戳秒数),过期时间
TTL:4字节,剩余存活时间秒数
Topic:4字节,信封主题
AESNonce:12字节随机数据,仅在对称加密时有效
Data:消息体
EnvNonce:8字节任意数据(用于PoW计算)
如果节点无法解密信封,那么节点对信封内的消息内容一无所知,单这并不影响节点将消息进行转发扩散。
Message消息
信封内的消息体解密后即得到消息内容。
Topic主题
每个信封上都有一个主题,注意主题可以仅使用部分前缀
Filter过滤器
filter即订阅-发布模型中的订阅者
PoW工作量证明
PoW的存在是为了反垃圾信息以及降低网络负担。计算PoW所付出的代价可以理解为抵扣节点为传播和存储信息锁花费的资源.
在whisperv5中,PoW定义为:BestBit是hash计算值的前导0个数
size是消息大小
TTL
具有高PoW的消息具有优先处理权。
whisper节点发送消息需要经过创建消息whisper.NewSentMessage()—->封装入信封msg.Wrap(msg)—->shh.Send(),消息的工作量证明就在第二步装入信封的时候进行计算。
func (e *Envelope) Seal(options *MessageParams) error {
var target, bestBit int // target是需要达到的目标前置0位数
if options.PoW == 0 {
// 将消息过期时间调整到工作量计算完成后
e.Expiry += options.WorkTime
} else {
// 根据公式 PoW = (2^BestBit) / (size * TTL) 从预设的PoW阈值反解出BestBit
target = e.powToFirstBit(options.PoW)
if target < 1 {
target = 1
}
}
buf := make([]byte, 64)
// Keccak256是SHA-3的一种,Keccak已可以抵御最小的复杂度为2n的攻击,其中N为散列的大小。它具有广泛的安全边际。至目前为止,第三方密码分析已经显示出Keccak没有严重的弱点
h := crypto.Keccak256(e.rlpWithoutNonce())
copy(buf[:32], h)
finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
for nonce := uint64(0); time.Now().UnixNano() < finish; {
for i := 0; i < 1024; i++ {
// 暴力尝试nonce值
binary.BigEndian.PutUint64(buf[56:], nonce)
d := new(big.Int).SetBytes(crypto.Keccak256(buf))
firstBit := math.FirstBitSet(d)
if firstBit > bestBit {
e.EnvNonce, bestBit = nonce, firstBit
// 当尝试得到满足条件的EnvNonce,计算完成
if target > 0 && bestBit >= target {
return nil
}
}
nonce++
}
}
if target > 0 && bestBit < target {
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
}
return nil
}
通信流程
whisper协议的实现位于包github.com/ethereum/go-ethereum/whisper,该包下面有多个版本实现,目前最新协议包是whisperv6.
whisper main loop
whisper-main-loop
whisper节点启动后产生两个分支:一个分支负责清理shh.envelopes中的过期消息
另一个分支(proccessQueue)从两个队列取出新接收到的消息,根据消息对应topic投放(Trigger)到对应接收者(filter),从而交付给上层应用进行处理
补充说下whisper里两个队列messageQueue,p2pMsgQueue的不同作用,messageQueue接收普通的广播消息,p2pMsgQueue接收点对点的直接消息,可绕过pow和ttl限制.
whisper protocol
whisper协议的具体实现里,代码流程也非常清晰:
whisper-peer-loop
每个peer连接成功后,产生两个goroutine,进行消息接收和广播:接收消息协程不断从连接中读取新消息,并且将消息暂存到shh.envelopes中,如果发现是一条未接收过的新消息,则将消息转发到对应的队列(messageQueue,p2pMsgQueue)
广播协程负责将该peer未接收过的消息(本节点认为该peer未接收过,并非peer一定没接收过,p2p网络其他节点可能已经将消息广播到该节点了)投递到该peer