(建议先看总体流程梳理 https://blog.csdn.net/laorenmen/article/details/85079678)
前面学习了Auth的流程部分,m_nextState 从 New -- AckAuth -- writeHello
现在就进入hello流程部分,首先是RLPXHandshake::transition 里面的 else if (m_nextState == WriteHello) 分支
m_io.reset(new RLPXFrameCoder(*this));
RLPStream s;
bytes packet;
s.swapOut(packet);
m_io->writeSingleFramePacket(&packet, m_handshakeOutBuffer);
这里依次创建RLPXFrameCoder对象,构造RLPStream帧,拷贝进bytes中,最后放入io的OutBuffer里面异步发送出去,也就是说经过了Auth后,后面的数据都被RLPXFrameCoder编码,用RLPStream帧结构进行数据发送。详细来看下第一句new 一个RLPXFrameCoder对象,会进入构造函数
RLPXFrameCoder::RLPXFrameCoder(RLPXHandshake const& _init):
m_impl(new RLPXFrameCoderImpl)
{
// 入参依次是 主动发起标志, 远端临时公钥,远端随机数,本端临时私钥,本端随机数,ack的密文 auth的密文
setup(_init.m_originated, _init.m_ecdheRemote, _init.m_remoteNonce, _init.m_ecdheLocal, _init.m_nonce, &_init.m_ackCipher, &_init.m_authCipher);
}
入参都加了注释,接着看下setup函数的实现
void RLPXFrameCoder::setup(bool _originated, h512 const& _remoteEphemeral, h256 const& _remoteNonce, KeyPair const& _ecdheLocal, h256 const& _nonce, bytesConstRef _ackCipher, bytesConstRef _authCipher)
{
bytes keyMaterialBytes(64);
bytesRef keyMaterial(&keyMaterialBytes);
// shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce))
Secret ephemeralShared; // 共享私钥
// Try agree ECDHE. This can fail due to invalid remote public key. In this
// case throw an exception to be caught RLPXHandshake::transition().
// 用本端临时私钥,对端临时公钥,基于ECDH算法,生成一个共享私钥
if (!crypto::ecdh::agree(_ecdheLocal.secret(), _remoteEphemeral, ephemeralShared))
BOOST_THROW_EXCEPTION(ECDHEError{});
//将共享私钥放入keyMeterial的0~31字节
// 此时的keyMeterial是 ecdhe-shared-secret
ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); //即ecdhe-shared-secret
//计算nonceMaterial,包含本地随机数和远端随机数,看是否主动,分别放置
h512 nonceMaterial;
h256 const& leftNonce = _originated ? _remoteNonce : _nonce;
h256 const& rightNonce = _originated ? _nonce : _remoteNonce;
leftNonce.ref().copyTo(nonceMaterial.ref().cropped(0, h256::size));
rightNonce.ref().copyTo(nonceMaterial.ref().cropped(h256::size, h256::size));
auto outRef(keyMaterial.cropped(h256::size, h256::size));
// outRef里面是 sha3(remoteNonce || nonce ) 放入到keyMeterial的32~63字节
// // 此时的keyMeterial是 ecdhe-shared-secret || sha3(nonce || initiator-nonce)
sha3(nonceMaterial.ref(), outRef); // output h(nonces)
// outRef里面是 sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) 放入到keyMeterial的32~63字节 即shared-secret
// 此时的keyMeterial是 ecdhe-shared-secret || shared-secret
sha3(keyMaterial, outRef); // output shared-secret // outRef 是新的共享私钥
// token: sha3(outRef, bytesRef(&token)); -> m_host (to be saved to disk)
// aes-secret = sha3(ecdhe-shared-secret || shared-secret)
// outRef里面是 sha3(ecdhe-shared-secret || sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce))) 即aes-secret
// keyMaterial里面是ecdhe-shared-secret || aes-secret
sha3(keyMaterial, outRef); // output aes-secret
assert(m_impl->frameEncKey.empty() && "ECDHE aggreed before!");
m_impl->frameEncKey.resize(h256::size);
memcpy(m_impl->frameEncKey.data(), outRef.data(), h256::size);
m_impl->frameDecKey.resize(h256::size);
memcpy(m_impl->frameDecKey.data(), outRef.data(), h256::size); //??? 这里的decKey和encKey是一样的,dec用户解密,enc用于加密,都是AES算法
h128 iv;
m_impl->frameEnc.SetKeyWithIV(m_impl->frameEncKey, h256::size, iv.data());
m_impl->frameDec.SetKeyWithIV(m_impl->frameDecKey, h256::size, iv.data());
// mac-secret = sha3(ecdhe-shared-secret || aes-secret)
// outRef里面是 sha3(ecdhe-shared-secret || sha3(ecdhe-shared-secret || sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)))) 即 mac-secret
// keyMaterial里面是ecdhe-shared-secret || mac-secret
sha3(keyMaterial, outRef); // output mac-secret
m_impl->macEncKey.resize(h256::size);
memcpy(m_impl->macEncKey.data(), outRef.data(), h256::size);
m_impl->macEnc.SetKey(m_impl->macEncKey, h256::size);
// Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init)
// ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack)
// Recipient egress-mac: sha3(mac-secret^initiator-nonce || auth-sent-ack)
// ingress-mac: sha3(mac-secret^recipient-nonce || auth-recvd-init)
// 计算发出的mac和 接收到的mac mac消息的组成就是上面注释的那样
(*(h256*)outRef.data() ^ _remoteNonce).ref().copyTo(keyMaterial);
bytesConstRef egressCipher = _originated ? _authCipher : _ackCipher;
keyMaterialBytes.resize(h256::size + egressCipher.size());
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size());
egressCipher.copyTo(keyMaterial.cropped(h256::size, egressCipher.size()));
m_impl->egressMac.Update(keyMaterial.data(), keyMaterial.size());
// recover mac-secret by re-xoring remoteNonce
(*(h256*)keyMaterial.data() ^ _remoteNonce ^ _nonce).ref().copyTo(keyMaterial);
bytesConstRef ingressCipher = _originated ? _ackCipher : _authCipher;
keyMaterialBytes.resize(h256::size + ingressCipher.size());
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size());
ingressCipher.copyTo(keyMaterial.cropped(h256::size, ingressCipher.size()));
m_impl->ingressMac.Update(keyMaterial.data(), keyMaterial.size());
}
整个流程看下来,即简单又复杂,简单就是拿着本端随机私钥,对端随机公钥,本端随机数,对端随机数,一遍遍的sha3 hash计算secret,有ecdhe-shared-secret, shared-secret, aes-secret mac-secret ingress-mac egress-mac,复杂就是要仔细分析outRef keyMaterial 里面的内容
可以预见,后面节点间的消息传递都要基于这些加密 认证信息之上了,代码也可以看出来,前面是用RLPXFrameCoder初始化了m_io, 最后是m_io->writeSingleFramePacket(&packet, m_handshakeOutBuffer); 将数据包加密后写入到握手buffer里
继续上面的,writeHello, 把自己的一些信息:协议版本 客户端版本 能力集 监听端口及NodeID
RLPStream s;
s.append((unsigned)HelloPacket).appendList(5)
<< dev::p2p::c_protocolVersion
<< m_host->m_clientVersion
<< m_host->caps()
<< m_host->listenPort()
<< m_host->id(); // NodeID,即公钥
下一步就是readHello了,readHello 代码很长,实际是连续收了两个包,一个header,一个hello报文,用hello报文中的内容,
调用m_host->startPeerSession 开始最关键的session创建
else if (m_nextState == ReadHello)
{
// Authenticate and decrypt initial hello frame with initial RLPXFrameCoder
// and request m_host to start session.
m_nextState = StartSession;
// read frame header 读取帧头部
unsigned const handshakeSize = 32;
m_handshakeInBuffer.resize(handshakeSize);
ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, handshakeSize), [this, self](boost::system::error_code ec, std::size_t)
{
......
/// authenticate and decrypt header 鉴定并且解密
if (!m_io->authAndDecryptHeader(bytesRef(m_handshakeInBuffer.data(), m_handshakeInBuffer.size())))
{
......
}
......
// 读取header,rlp格式
/// rlp of header has protocol-type, sequence-id[, total-packet-size]
bytes headerRLP(header.size() - 3 - h128::size); // this is always 32 - 3 - 16 = 13. wtf?
bytesConstRef(&header).cropped(3).copyTo(&headerRLP);
//读取数据帧和mac
/// read padded frame and mac
m_handshakeInBuffer.resize(frameSize + ((16 - (frameSize % 16)) % 16) + h128::size);
ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, m_handshakeInBuffer.size()), [this, self, headerRLP](boost::system::error_code ec, std::size_t)
{
......
bytesRef frame(&m_handshakeInBuffer);
if (!m_io->authAndDecryptFrame(frame)) //认证和解密帧
{
......
}
//判断是hello报文
PacketType packetType = frame[0] == 0x80 ? HelloPacket : (PacketType)frame[0];
......
try
{
//到此启动session
RLP rlp(frame.cropped(1), RLP::ThrowOnFail | RLP::FailIfTooSmall);
m_host->startPeerSession(m_remote, rlp, move(m_io), m_socket);
}
catch (std::exception const& _e)
{
cnetlog << "Handshake causing an exception: " << _e.what();
m_nextState = Error;
transition();
}
}
});
}
});
}
很明确,这里创建session了。
下一节先重点看下host的组成