(建议先看总体流程梳理 https://blog.csdn.net/laorenmen/article/details/85079678)
在前面说过以太坊P2P分两层,一层是基于UDP的,实现了Kad算法,用于节点发现,NodeTable类是其主要接口。另一层是基于TCP,用于节点间的消息传递处理,这里贯穿流程的接口就是Host类,后面我们会来看看这一层的代码细节,
这里先看下其中一个关键独立步骤,握手,后面涉及到握手流程,就可以直接用“握手”两个字代替了,不理解了再单独看这一节
class RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
{
friend class RLPXFrameCoder;
public:
//两种构造函数,第一种不知道对端NodeID, 置m_originated为false, 第二种主动握手,知道NodeID, 置m_originated为true
/// Setup incoming connection.
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket): m_host(_host), m_originated(false), m_socket(_socket), m_idleTimer(m_socket->ref().get_io_service()) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); }
/// Setup outbound connection.
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket, NodeID _remote): m_host(_host), m_remote(_remote), m_originated(true), m_socket(_socket), m_idleTimer(m_socket->ref().get_io_service()) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); }
virtual ~RLPXHandshake() = default;
/// Start handshake.
void start() { transition(); }
/// Aborts the handshake.
void cancel();
protected:
// 握手协议包含的状态
/// Sequential states of handshake
enum State
{
Error = -1,
New,
AckAuth,
AckAuthEIP8,
WriteHello,
ReadHello,
StartSession
};
//写Auth到Socket,并置状态为AckAuth
/// Write Auth message to socket and transitions to AckAuth.
void writeAuth();
//读取Auth从Socket,并置状态为AckAuth
/// Reads Auth message from socket and transitions to AckAuth.
void readAuth();
//读取Auth的EIP-8格式,并置状态为AckAuthEIP8
/// Continues reading Auth message in EIP-8 format and transitions to AckAuthEIP8.
void readAuthEIP8();
// 从签名中得到临时加密信息,并在Auth被解密后保存下来。。。需继续理解
/// Derives ephemeral secret from signature and sets members after Auth has been decrypted.
void setAuthValues(Signature const& sig, Public const& remotePubk, h256 const& remoteNonce, uint64_t remoteVersion);
// 向Socket写入Ack,并置状态为WriteHello
/// Write Ack message to socket and transitions to WriteHello.
void writeAck();
// 向Socket写入EIP-8格式的Ack,并置状态为WriteHello
/// Write Ack message in EIP-8 format to socket and transitions to WriteHello.
void writeAckEIP8();
// 从Socket中读取Ack,并置状态为WriteHello
/// Reads Auth message from socket and transitions to WriteHello.
void readAck();
// 从Socket中读取EIP-8格式的Ack,并置状态为WriteHello
/// Continues reading Ack message in EIP-8 format and transitions to WriteHello.
void readAckEIP8();
/// Closes connection and ends transitions.
void error();
// 执行m_nextState的转换
/// Performs transition for m_nextState.
virtual void transition(boost::system::error_code _ech = boost::system::error_code());
// 远端响应转换事件的超时定时,由m_idleTimer使用,并在transition()中刷新
/// Timeout for remote to respond to transition events. Enforced by m_idleTimer and refreshed by transition().
boost::posix_time::milliseconds const c_timeout = boost::posix_time::milliseconds(1800);
//当前或者期望的下一个状态
State m_nextState = New; ///< Current or expected state of transition.
// 连接关闭置为true
bool m_cancel = false; ///< Will be set to true if connection was canceled.
// host
Host* m_host; ///< Host which provides m_alias, protocolVersion(), m_clientVersion, caps(), and TCP listenPort().
// 远端节点ID,即Public key
/// Node id of remote host for socket.
NodeID m_remote; ///< Public address of remote host.
// 标记主动握手还是被动
bool m_originated = false; ///< True if connection is outbound.
/// Buffers for encoded and decoded handshake phases
// 发出或者接受到的Auth消息的明文
bytes m_auth; ///< Plaintext of egress or ingress Auth message.
// 发出或者接受到的Auth消息的密文
bytes m_authCipher; ///< Ciphertext of egress or ingress Auth message.
// 发出或者接受到的Ack消息的明文
bytes m_ack; ///< Plaintext of egress or ingress Ack message.
// 发出或者接受到的Ack消息的密文
bytes m_ackCipher; ///< Ciphertext of egress or ingress Ack message.
// 发出的Hello包的buffer
bytes m_handshakeOutBuffer; ///< Frame buffer for egress Hello packet.
// 接受的Hello包的buffer
bytes m_handshakeInBuffer; ///< Frame buffer for ingress Hello packet.
//临时秘钥交换算法协议
KeyPair m_ecdheLocal = KeyPair::create(); ///< Ephemeral ECDH secret and agreement.
// host用于握手生成的随机值
h256 m_nonce; ///< Nonce generated by this host for handshake.
// 远端临时公钥
Public m_ecdheRemote; ///< Remote ephemeral public key.
// 远端握手的随机值
h256 m_remoteNonce; ///< Nonce generated by remote host for handshake.
//远端版本
uint64_t m_remoteVersion;
/// Used to read and write RLPx encrypted frames for last step of handshake authentication.
/// Passed onto Host which will take ownership.
// 用于握手认证最后一步,读取或者写入RLPx的加密帧
std::unique_ptr<RLPXFrameCoder> m_io;
// TCP Socket
std::shared_ptr<RLPXSocket> m_socket; ///< Socket.
// 超时定时器
boost::asio::deadline_timer m_idleTimer; ///< Timer which enforces c_timeout.
Logger m_logger{createLogger(VerbosityTrace, "net")};
};
很多地方都加了注释,通过注释大体了解,ShakeHand是实现了两个已连接节点的会话状态,从握手开始到断开消息传递
注意的点
1. 两个构造函数的不同,一个是主动,一个是被动,由m_originated标识
2. 握手时序状态 New - AckAuth - WriteHello - ReadHello - StartSession,状态转换由transition函数处理,创建一个RLPXHandshake对象后,执行它的start(),进而开始状态的转换
如果是主动握手:writeAuth --- readAck --- writeHello --- readHello
被动握手:readAuth --- writeAck --- writeHello --- readHello
m_nextState: New -- AckAuth -- WriteHello -- ReadHello -- StartSession
整个握手流程分为了Auth和Hello两个阶段
void RLPXHandshake::writeAuth()
{
LOG(m_logger) << "p2p.connect.egress sending auth to " << m_socket->remoteEndpoint();
m_auth.resize(Signature::size + h256::size + Public::size + h256::size + 1);
bytesRef sig(&m_auth[0], Signature::size);
bytesRef hepubk(&m_auth[Signature::size], h256::size);
bytesRef pubk(&m_auth[Signature::size + h256::size], Public::size);
bytesRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size);
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
// 最终加密发送的消息构成是用远端的公钥加密的下文:
// 用随机私钥对共享私钥和随机数组合进行的签名 | 随机公钥的hash | 本段公钥 | 随机数 | 0x0
Secret staticShared;
// 自己的私钥 对端的公钥,生成一个共享的私钥
crypto::ecdh::agree(m_host->m_alias.secret(), m_remote, staticShared);
// 随机私钥对共享私钥和随机数组合进行签名
sign(m_ecdheLocal.secret(), staticShared.makeInsecure() ^ m_nonce).ref().copyTo(sig);
// 随机公钥的Hash值
sha3(m_ecdheLocal.pub().ref(), hepubk);
// 本段公钥
m_host->m_alias.pub().ref().copyTo(pubk);
// 随机数
m_nonce.ref().copyTo(nonce);
// 最后1bit是0x0
m_auth[m_auth.size() - 1] = 0x0;
// 用远端公钥进行加密
encryptECIES(m_remote, &m_auth, m_authCipher);
auto self(shared_from_this());
ba::async_write(m_socket->ref(), ba::buffer(m_authCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
// 上面写认证信息,这里进行读取解析
void RLPXHandshake::readAuth()
{
LOG(m_logger) << "p2p.connect.ingress receiving auth from " << m_socket->remoteEndpoint();
m_authCipher.resize(307);
auto self(shared_from_this());
ba::async_read(m_socket->ref(), ba::buffer(m_authCipher, 307), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
// 用本端私钥进行解密 (前面是用我本端的公钥进行加密的),得到m_auth
else if (decryptECIES(m_host->m_alias.secret(), bytesConstRef(&m_authCipher), m_auth))
{
bytesConstRef data(&m_auth);
// 分离m_auth的参数
Signature sig(data.cropped(0, Signature::size)); // 签名,是远端临时私钥对 共享秘钥和随机数的组合进行的签名
Public pubk(data.cropped(Signature::size + h256::size, Public::size));// 远端公钥
h256 nonce(data.cropped(Signature::size + h256::size + Public::size, h256::size));//远端发过来的随机数
setAuthValues(sig, pubk, nonce, 4);
transition();
}
else
readAuthEIP8();
});
}
// 从签名中解出远端的随机公钥 ??? 这里不是很明白
void RLPXHandshake::setAuthValues(Signature const& _sig, Public const& _remotePubk, h256 const& _remoteNonce, uint64_t _remoteVersion)
{
_remotePubk.ref().copyTo(m_remote.ref());
_remoteNonce.ref().copyTo(m_remoteNonce.ref());
m_remoteVersion = _remoteVersion;
Secret sharedSecret;
// 根据ECDH的特性,由本端的私钥和远端的公钥,可以恢复计算出共享私钥来
crypto::ecdh::agree(m_host->m_alias.secret(), _remotePubk, sharedSecret);
// 反解 共享私钥和随机数组合的签名。。。。就能得到远端的临时随机公钥????
m_ecdheRemote = recover(_sig, sharedSecret.makeInsecure() ^ _remoteNonce);
}
首先,这里第一步涉及到一个生成共享私钥的算法,使用的事ECDH算法,即基于椭圆曲线的DH秘钥交换算法,具体算法理解如下:
前提,双方共享椭圆曲线的基点G, 椭圆曲线E 阶 N,即是相同的椭圆曲线算法
Alice,生成自己的随机秘钥,即私钥a, a * G == A 是Alice的公钥
Bob, 生成自己的随机私钥,即私钥b, b * G == B 是Bob的公钥
公钥可以在网路上传播,Alice Bob 互相收到对方公钥,即有
则有 a * B = a * b * G = b * a * G = b * A ==== K 共享私钥 对应上面代码:
crypto::ecdh::agree(m_host->m_alias.secret(), m_remote, staticShared); // 用自己的私钥,对方的公钥,生成一个共享私钥
这样对端只要拿到本端的公钥,结合它的私钥,就能恢复计算出共享私钥,在函数setAuthValues里有计算。
这里遗留了几个疑问:
1. m_auth的组合是 用随机私钥对共享私钥和随机数组合进行的签名 | 随机公钥的hash | 本段公钥 | 随机数 | 0x0
对端反解时,按说也能解出随机公钥的hash,没看到解析出来,也没看到哪里使用
2. 对共享私钥和随机数组合进行签名 sign(m_ecdheLocal.secret(), staticShared.makeInsecure() ^ m_nonce).ref().copyTo(sig);
用的是临时私钥进行签名的,后面再解析的时候,可以先计算出共享私钥和随机数的组合来,怎么就能反解出临时公钥来呢???
m_ecdheRemote = recover(_sig, sharedSecret.makeInsecure() ^ _remoteNonce);
可能是我签名算法跟恢复算法没看太懂,所以有了疑问,有哪位大神指导的,希望指点下小弟,谢谢。
好了,暂时先放下疑问,继续往下学,A端先主动发送Auth,即WriteAuth,B端收到后,会ReadAuth,然后,WriteAck,把B端的随机公钥和随机数用A端的公钥进行加密,返回回去。 A端读取Ack,即ReadAck,把B端的随机公钥和随机数保存下来
void RLPXHandshake::writeAck()
{
LOG(m_logger) << "p2p.connect.ingress sending ack to " << m_socket->remoteEndpoint();
m_ack.resize(Public::size + h256::size + 1);
bytesRef epubk(&m_ack[0], Public::size);
bytesRef nonce(&m_ack[Public::size], h256::size);
// 本端临时公钥
m_ecdheLocal.pub().ref().copyTo(epubk);
// 本端的随机数
m_nonce.ref().copyTo(nonce);
m_ack[m_ack.size() - 1] = 0x0;
// 远端公钥加密
encryptECIES(m_remote, &m_ack, m_ackCipher);
auto self(shared_from_this());
ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
//对端发过来Ack, 本端接收
void RLPXHandshake::readAck()
{
LOG(m_logger) << "p2p.connect.egress receiving ack from " << m_socket->remoteEndpoint();
m_ackCipher.resize(210);
auto self(shared_from_this());
ba::async_read(m_socket->ref(), ba::buffer(m_ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
// 本端私钥解密,得到Ack明文
else if (decryptECIES(m_host->m_alias.secret(), bytesConstRef(&m_ackCipher), m_ack))
{
// 从ack明文中得到远端的随机公钥和随机数,保存下来
bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_ecdheRemote.ref());
bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref());
m_remoteVersion = 4;
transition();
}
else
readAckEIP8();
});
}
好了,握手的前半部分Auth交互完成了,先到这里,下一节在继续学习其他部分