以太坊P2P 流程(3)

(建议先看总体流程梳理 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的组成

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值