以太坊P2P 流程(2)

(建议先看总体流程梳理 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交互完成了,先到这里,下一节在继续学习其他部分

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值