예제 #1
0
파일: peer.py 프로젝트: caesarchad/py-evm
    def __init__(self,
                 remote: Node,
                 privkey: datatypes.PrivateKey,
                 reader: asyncio.StreamReader,
                 writer: asyncio.StreamWriter,
                 aes_secret: bytes,
                 mac_secret: bytes,
                 egress_mac: sha3.keccak_256,
                 ingress_mac: sha3.keccak_256,
                 headerdb: 'BaseAsyncHeaderDB',
                 network_id: int,
                 inbound: bool = False,
                 ) -> None:
        super().__init__()
        self.remote = remote
        self.privkey = privkey
        self.reader = reader
        self.writer = writer
        self.base_protocol = P2PProtocol(self)
        self.headerdb = headerdb
        self.network_id = network_id
        self.inbound = inbound
        self._subscribers: List['asyncio.Queue[PEER_MSG_TYPE]'] = []

        self.egress_mac = egress_mac
        self.ingress_mac = ingress_mac
        # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32
        iv = b"\x00" * 16
        aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend())
        self.aes_enc = aes_cipher.encryptor()
        self.aes_dec = aes_cipher.decryptor()
        mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend())
        self.mac_enc = mac_cipher.encryptor().update
예제 #2
0
    async def process_p2p_handshake(self, cmd: protocol.Command,
                                    msg: protocol.PayloadType) -> None:
        msg = cast(Dict[str, Any], msg)
        if not isinstance(cmd, Hello):
            await self.disconnect(DisconnectReason.bad_protocol)
            raise HandshakeFailure(
                f"Expected a Hello msg, got {cmd}, disconnecting")

        # Check whether to support Snappy Compression or not
        # based on other peer's p2p protocol version
        snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION

        if snappy_support:
            # Now update the base protocol to support snappy compression
            # This is needed so that Trinity is compatible with parity since
            # parity sends Ping immediately after Handshake
            self.base_protocol = P2PProtocol(self,
                                             snappy_support=snappy_support)

        remote_capabilities = msg['capabilities']
        try:
            self.sub_proto = self.select_sub_protocol(remote_capabilities,
                                                      snappy_support)
        except NoMatchingPeerCapabilities:
            await self.disconnect(DisconnectReason.useless_peer)
            raise HandshakeFailure(
                f"No matching capabilities between us ({self.capabilities}) and {self.remote} "
                f"({remote_capabilities}), disconnecting")

        self.logger.debug(
            "Finished P2P handshake with %s, using sub-protocol %s",
            self.remote, self.sub_proto)
예제 #3
0
파일: peer.py 프로젝트: sunfinite/py-evm
    def __init__(self,
                 remote: Node,
                 privkey: datatypes.PrivateKey,
                 reader: asyncio.StreamReader,
                 writer: asyncio.StreamWriter,
                 aes_secret: bytes,
                 mac_secret: bytes,
                 egress_mac: sha3.keccak_256,
                 ingress_mac: sha3.keccak_256,
                 chaindb: AsyncChainDB,
                 network_id: int,
                 ) -> None:
        self._finished = asyncio.Event()
        self.remote = remote
        self.privkey = privkey
        self.reader = reader
        self.writer = writer
        self.base_protocol = P2PProtocol(self)
        self.chaindb = chaindb
        self.network_id = network_id
        self.sub_proto_msg_queue = asyncio.Queue()  # type: asyncio.Queue[Tuple[protocol.Command, protocol._DecodedMsgType]]  # noqa: E501
        self.cancel_token = CancelToken('Peer')

        self.egress_mac = egress_mac
        self.ingress_mac = ingress_mac
        # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32
        iv = b"\x00" * 16
        aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend())
        self.aes_enc = aes_cipher.encryptor()
        self.aes_dec = aes_cipher.decryptor()
        mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend())
        self.mac_enc = mac_cipher.encryptor().update
예제 #4
0
    def __init__(self,
                 remote: Node,
                 privkey: datatypes.PrivateKey,
                 connection: PeerConnection,
                 context: BasePeerContext,
                 inbound: bool = False,
                 token: CancelToken = None,
                 ) -> None:
        super().__init__(token)

        # Any contextual information the peer may need.
        self.context = context

        # The `Node` that this peer is connected to
        self.remote = remote

        # The private key this peer uses for identification and encryption.
        self.privkey = privkey

        # The self-identifying string that the remote names itself.
        self.client_version_string = ''

        # Networking reader and writer objects for communication
        self.reader = connection.reader
        self.writer = connection.writer
        # Initially while doing the handshake, the base protocol shouldn't support
        # snappy compression
        self.base_protocol = P2PProtocol(self, snappy_support=False)

        # Flag indicating whether the connection this peer represents was
        # established from a dial-out or dial-in (True: dial-in, False:
        # dial-out)
        # TODO: rename to `dial_in` and have a computed property for `dial_out`
        self.inbound = inbound
        self._subscribers: List[PeerSubscriber] = []

        # Uptime tracker for how long the peer has been running.
        # TODO: this should move to begin within the `_run` method (or maybe as
        # part of the `BaseService` API)
        self.start_time = datetime.datetime.now()

        # A counter of the number of messages this peer has received for each
        # message type.
        self.received_msgs: Dict[protocol.Command, int] = collections.defaultdict(int)

        # Encryption and Cryptography *stuff*
        self.egress_mac = connection.egress_mac
        self.ingress_mac = connection.ingress_mac
        # FIXME: Insecure Encryption: https://github.com/ethereum/devp2p/issues/32
        iv = b"\x00" * 16
        aes_secret = connection.aes_secret
        mac_secret = connection.mac_secret
        aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv), default_backend())
        self.aes_enc = aes_cipher.encryptor()
        self.aes_dec = aes_cipher.decryptor()
        mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(), default_backend())
        self.mac_enc = mac_cipher.encryptor().update

        # Manages the boot process
        self.boot_manager = self.get_boot_manager()
예제 #5
0
    async def process_p2p_handshake(self, cmd: Command,
                                    msg: PayloadType) -> None:
        msg = cast(Dict[str, Any], msg)
        if not isinstance(cmd, Hello):
            await self.disconnect(DisconnectReason.bad_protocol)
            raise HandshakeFailure(
                f"Expected a Hello msg, got {cmd}, disconnecting")

        # limit number of chars to be displayed, and try to keep printable ones only
        # MAGIC 256: arbitrary, "should be enough for everybody"
        original_version = msg['client_version_string']
        client_version_string = original_version[:256] + (
            '...' if original_version[256:] else '')
        if client_version_string.isprintable():
            self.client_version_string = client_version_string.strip()
        else:
            self.client_version_string = repr(client_version_string)

        # Check whether to support Snappy Compression or not
        # based on other peer's p2p protocol version
        snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION

        if snappy_support:
            # Now update the base protocol to support snappy compression
            # This is needed so that Trinity is compatible with parity since
            # parity sends Ping immediately after Handshake
            self.base_protocol = P2PProtocol(
                self.transport,
                snappy_support=snappy_support,
                capabilities=self.capabilities,
                listen_port=self.listen_port,
            )

        remote_capabilities = msg['capabilities']
        matched_proto_classes = match_protocols_with_capabilities(
            self.supported_sub_protocols,
            remote_capabilities,
        )
        if len(matched_proto_classes) == 1:
            self.sub_proto = matched_proto_classes[0](
                self.transport,
                self.base_protocol.cmd_length,
                snappy_support,
            )
        elif len(matched_proto_classes) > 1:
            raise NotImplementedError(
                f"Peer {self.remote} connection matched on multiple protocols "
                f"{matched_proto_classes}.  Support for multiple protocols is not "
                f"yet supported")
        else:
            await self.disconnect(DisconnectReason.useless_peer)
            raise HandshakeFailure(
                f"No matching capabilities between us ({self.capabilities}) and {self.remote} "
                f"({remote_capabilities}), disconnecting")

        self.logger.debug(
            "Finished P2P handshake with %s, using sub-protocol %s",
            self.remote, self.sub_proto)
예제 #6
0
def MultiplexerPairFactory(*,
                           protocol_types: Tuple[Type[ProtocolAPI], ...] = (),
                           transport_factory: Callable[..., TransportPair] = MemoryTransportPairFactory,  # noqa: E501
                           alice_remote: NodeAPI = None,
                           alice_private_key: keys.PrivateKey = None,
                           bob_remote: NodeAPI = None,
                           bob_private_key: keys.PrivateKey = None,
                           snappy_support: bool = False,
                           cancel_token: CancelToken = None,
                           ) -> Tuple[MultiplexerAPI, MultiplexerAPI]:
    alice_transport, bob_transport = transport_factory(
        alice_remote=alice_remote,
        alice_private_key=alice_private_key,
        bob_remote=bob_remote,
        bob_private_key=bob_private_key,
    )
    cmd_id_offsets = get_cmd_offsets(protocol_types)
    alice_protocols = tuple(
        protocol_class(alice_transport, offset, snappy_support)
        for protocol_class, offset
        in zip(protocol_types, cmd_id_offsets)
    )
    bob_protocols = tuple(
        protocol_class(bob_transport, offset, snappy_support)
        for protocol_class, offset
        in zip(protocol_types, cmd_id_offsets)
    )

    alice_p2p_protocol = P2PProtocol(alice_transport, snappy_support)
    alice_multiplexer = Multiplexer(
        transport=alice_transport,
        base_protocol=alice_p2p_protocol,
        protocols=alice_protocols,
        token=cancel_token,
    )

    bob_p2p_protocol = P2PProtocol(bob_transport, False)
    bob_multiplexer = Multiplexer(
        transport=bob_transport,
        base_protocol=bob_p2p_protocol,
        protocols=bob_protocols,
        token=cancel_token,
    )
    return alice_multiplexer, bob_multiplexer
예제 #7
0
async def _do_p2p_handshake(
        transport: TransportAPI, capabilites: Capabilities,
        p2p_handshake_params: DevP2PHandshakeParams,
        base_protocol: BaseP2PProtocol,
        token: CancelToken) -> Tuple[DevP2PReceipt, BaseP2PProtocol]:
    client_version_string, listen_port, p2p_version = p2p_handshake_params
    base_protocol.send_handshake(client_version_string, capabilites,
                                 listen_port, p2p_version)

    # The base `p2p` protocol handshake directly streams the messages as it has
    # strict requirements about receiving the `Hello` message first.
    async for _, cmd, msg in stream_transport_messages(transport,
                                                       base_protocol,
                                                       token=token):
        if not isinstance(cmd, Hello):
            raise HandshakeFailure(
                f"First message across the DevP2P connection must be a Hello "
                f"msg, got {cmd}, disconnecting")

        msg = cast(Dict[str, Any], msg)

        protocol: BaseP2PProtocol

        if base_protocol.version >= DEVP2P_V5:
            # Check whether to support Snappy Compression or not
            # based on other peer's p2p protocol version
            snappy_support = msg['version'] >= DEVP2P_V5

            if snappy_support:
                # Now update the base protocol to support snappy compression
                # This is needed so that Trinity is compatible with parity since
                # parity sends Ping immediately after handshake
                protocol = P2PProtocol(
                    transport,
                    cmd_id_offset=0,
                    snappy_support=True,
                )
            else:
                protocol = base_protocol
        else:
            protocol = base_protocol

        devp2p_receipt = DevP2PReceipt(
            protocol=protocol,
            version=msg['version'],
            client_version_string=msg['client_version_string'],
            capabilities=msg['capabilities'],
            remote_public_key=msg['remote_pubkey'],
            listen_port=msg['listen_port'],
        )
        break
    else:
        raise HandshakeFailure(
            "DevP2P message stream exited before finishing handshake")

    return devp2p_receipt, protocol
예제 #8
0
    def __init__(
        self,
        transport: Transport,
        context: BasePeerContext,
        inbound: bool = False,
        event_bus: AsyncioEndpoint = None,
        token: CancelToken = None,
    ) -> None:
        super().__init__(token)

        # Transport instance for network communications
        self.transport = transport

        # Any contextual information the peer may need.
        self.context = context

        # The self-identifying string that the remote names itself.
        self.client_version_string = ''

        # Initially while doing the handshake, the base protocol shouldn't support
        # snappy compression
        self.base_protocol = P2PProtocol(
            transport=self.transport,
            snappy_support=False,
            capabilities=self.capabilities,
            listen_port=self.listen_port,
        )

        # Optional event bus handle
        self._event_bus = event_bus

        # Flag indicating whether the connection this peer represents was
        # established from a dial-out or dial-in (True: dial-in, False:
        # dial-out)
        # TODO: rename to `dial_in` and have a computed property for `dial_out`
        self.inbound = inbound
        self._subscribers: List[PeerSubscriber] = []

        # A counter of the number of messages this peer has received for each
        # message type.
        self.received_msgs: Dict[Command, int] = collections.defaultdict(int)

        # Manages the boot process
        self.boot_manager = self.get_boot_manager()
        self.connection_tracker = self.setup_connection_tracker()
예제 #9
0
    async def process_p2p_handshake(self, cmd: protocol.Command,
                                    msg: protocol.PayloadType) -> None:
        msg = cast(Dict[str, Any], msg)
        if not isinstance(cmd, Hello):
            await self.disconnect(DisconnectReason.bad_protocol)
            raise HandshakeFailure(
                f"Expected a Hello msg, got {cmd}, disconnecting")

        # limit number of chars to be displayed, and try to keep printable ones only
        # MAGIC 256: arbitrary, "should be enough for everybody"
        original_version = msg['client_version_string']
        client_version_string = original_version[:256] + (
            '...' if original_version[256:] else '')
        if client_version_string.isprintable():
            self.client_version_string = client_version_string.strip()
        else:
            self.client_version_string = repr(client_version_string)

        # Check whether to support Snappy Compression or not
        # based on other peer's p2p protocol version
        snappy_support = msg['version'] >= SNAPPY_PROTOCOL_VERSION

        if snappy_support:
            # Now update the base protocol to support snappy compression
            # This is needed so that Trinity is compatible with parity since
            # parity sends Ping immediately after Handshake
            self.base_protocol = P2PProtocol(self,
                                             snappy_support=snappy_support)

        remote_capabilities = msg['capabilities']
        try:
            self.sub_proto = self.select_sub_protocol(remote_capabilities,
                                                      snappy_support)
        except NoMatchingPeerCapabilities:
            await self.disconnect(DisconnectReason.useless_peer)
            raise HandshakeFailure(
                f"No matching capabilities between us ({self.capabilities}) and {self.remote} "
                f"({remote_capabilities}), disconnecting")

        self.logger.debug(
            "Finished P2P handshake with %s, using sub-protocol %s",
            self.remote, self.sub_proto)
예제 #10
0
파일: peer.py 프로젝트: jin10086/py-evm
    def __init__(
        self,
        remote: Node,
        privkey: datatypes.PrivateKey,
        reader: asyncio.StreamReader,
        writer: asyncio.StreamWriter,
        aes_secret: bytes,
        mac_secret: bytes,
        egress_mac: sha3.keccak_256,
        ingress_mac: sha3.keccak_256,
        headerdb: 'BaseAsyncHeaderDB',
        network_id: int,
        inbound: bool = False,
        token: CancelToken = None,
    ) -> None:
        super().__init__(token)
        self.remote = remote
        self.privkey = privkey
        self.reader = reader
        self.writer = writer
        self.base_protocol = P2PProtocol(self)
        self.headerdb = headerdb
        self.network_id = network_id
        self.inbound = inbound
        self._subscribers: List[PeerSubscriber] = []
        self.start_time = datetime.datetime.now()
        self.received_msgs: Dict[protocol.Command,
                                 int] = collections.defaultdict(int)
        self.booted = asyncio.Event()

        self.egress_mac = egress_mac
        self.ingress_mac = ingress_mac
        # FIXME: Yes, the encryption is insecure, see: https://github.com/ethereum/devp2p/issues/32
        iv = b"\x00" * 16
        aes_cipher = Cipher(algorithms.AES(aes_secret), modes.CTR(iv),
                            default_backend())
        self.aes_enc = aes_cipher.encryptor()
        self.aes_dec = aes_cipher.decryptor()
        mac_cipher = Cipher(algorithms.AES(mac_secret), modes.ECB(),
                            default_backend())
        self.mac_enc = mac_cipher.encryptor().update
예제 #11
0
 def __init__(self, supported_sub_protocols):
     self._supported_sub_protocols = supported_sub_protocols
     self.base_protocol = P2PProtocol(self)
예제 #12
0
파일: test_peer.py 프로젝트: mhchia/trinity
 def __init__(self, supported_sub_protocols, snappy_support):
     self.supported_sub_protocols = supported_sub_protocols
     self.base_protocol = P2PProtocol(self, snappy_support)
예제 #13
0
async def test_handshake():
    # TODO: this test should be re-written to not depend on functionality in the `ETHPeer` class.
    cancel_token = CancelToken("test_handshake")
    use_eip8 = False
    initiator_remote = kademlia.Node(
        keys.PrivateKey(test_values['receiver_private_key']).public_key,
        kademlia.Address('0.0.0.0', 0, 0))
    initiator = HandshakeInitiator(
        initiator_remote,
        keys.PrivateKey(test_values['initiator_private_key']), use_eip8,
        cancel_token)
    initiator.ephemeral_privkey = keys.PrivateKey(
        test_values['initiator_ephemeral_private_key'])

    responder_remote = kademlia.Node(
        keys.PrivateKey(test_values['initiator_private_key']).public_key,
        kademlia.Address('0.0.0.0', 0, 0))
    responder = HandshakeResponder(
        responder_remote, keys.PrivateKey(test_values['receiver_private_key']),
        use_eip8, cancel_token)
    responder.ephemeral_privkey = keys.PrivateKey(
        test_values['receiver_ephemeral_private_key'])

    # Check that the auth message generated by the initiator is what we expect. Notice that we
    # can't use the auth_init generated here because the non-deterministic prefix would cause the
    # derived secrets to not match the expected values.
    _auth_init = initiator.create_auth_message(test_values['initiator_nonce'])
    assert len(_auth_init) == len(test_values['auth_plaintext'])
    assert _auth_init[65:] == test_values['auth_plaintext'][
        65:]  # starts with non deterministic k

    # Check that encrypting and decrypting the auth_init gets us the orig msg.
    _auth_init_ciphertext = initiator.encrypt_auth_message(_auth_init)
    assert _auth_init == ecies.decrypt(_auth_init_ciphertext,
                                       responder.privkey)

    # Check that the responder correctly decodes the auth msg.
    auth_msg_ciphertext = test_values['auth_ciphertext']
    initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication(
        auth_msg_ciphertext, responder.privkey)
    assert initiator_nonce == test_values['initiator_nonce']
    assert initiator_ephemeral_pubkey == (keys.PrivateKey(
        test_values['initiator_ephemeral_private_key']).public_key)

    # Check that the auth_ack msg generated by the responder is what we expect.
    auth_ack_msg = responder.create_auth_ack_message(
        test_values['receiver_nonce'])
    assert auth_ack_msg == test_values['authresp_plaintext']

    # Check that the secrets derived from ephemeral key agreements match the expected values.
    auth_ack_ciphertext = test_values['authresp_ciphertext']
    aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets(
        initiator_nonce, test_values['receiver_nonce'],
        initiator_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext)
    assert aes_secret == test_values['aes_secret']
    assert mac_secret == test_values['mac_secret']
    # Test values are from initiator perspective, so they're reversed here.
    assert ingress_mac.digest() == test_values['initial_egress_MAC']
    assert egress_mac.digest() == test_values['initial_ingress_MAC']

    # Check that the initiator secrets match as well.
    responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message(
        test_values['authresp_ciphertext'])
    (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac,
     initiator_ingress_mac) = initiator.derive_secrets(
         initiator_nonce, responder_nonce, responder_ephemeral_pubkey,
         auth_msg_ciphertext, auth_ack_ciphertext)
    assert initiator_aes_secret == aes_secret
    assert initiator_mac_secret == mac_secret
    assert initiator_ingress_mac.digest() == test_values['initial_ingress_MAC']
    assert initiator_egress_mac.digest() == test_values['initial_egress_MAC']

    # Finally, check that two Peers configured with the secrets generated above understand each
    # other.
    (
        (responder_reader, responder_writer),
        (initiator_reader, initiator_writer),
    ) = get_directly_connected_streams()

    capabilities = (('paragon', 1), )

    initiator_transport = Transport(remote=initiator_remote,
                                    private_key=initiator.privkey,
                                    reader=initiator_reader,
                                    writer=initiator_writer,
                                    aes_secret=initiator_aes_secret,
                                    mac_secret=initiator_mac_secret,
                                    egress_mac=initiator_egress_mac,
                                    ingress_mac=initiator_ingress_mac)
    initiator_p2p_protocol = P2PProtocol(initiator_transport, 0, False)
    initiator_multiplexer = Multiplexer(
        transport=initiator_transport,
        base_protocol=initiator_p2p_protocol,
        protocols=(),
    )
    initiator_multiplexer.get_base_protocol().send_handshake(
        'initiator',
        capabilities,
        30303,
        DEVP2P_V5,
    )

    responder_transport = Transport(
        remote=responder_remote,
        private_key=responder.privkey,
        reader=responder_reader,
        writer=responder_writer,
        aes_secret=aes_secret,
        mac_secret=mac_secret,
        egress_mac=egress_mac,
        ingress_mac=ingress_mac,
    )
    responder_p2p_protocol = P2PProtocol(responder_transport, 0, False)
    responder_multiplexer = Multiplexer(
        transport=responder_transport,
        base_protocol=responder_p2p_protocol,
        protocols=(),
    )
    responder_multiplexer.get_base_protocol().send_handshake(
        'responder',
        capabilities,
        30303,
        DEVP2P_V5,
    )

    async with initiator_multiplexer.multiplex():
        async with responder_multiplexer.multiplex():
            initiator_stream = initiator_multiplexer.stream_protocol_messages(
                initiator_p2p_protocol, )
            responder_stream = responder_multiplexer.stream_protocol_messages(
                responder_p2p_protocol, )

            initiator_hello, _ = await asyncio.wait_for(
                initiator_stream.asend(None), timeout=0.1)
            responder_hello, _ = await asyncio.wait_for(
                responder_stream.asend(None), timeout=0.1)

            await initiator_stream.aclose()
            await responder_stream.aclose()

    assert isinstance(responder_hello, Hello)
    assert isinstance(initiator_hello, Hello)
예제 #14
0
async def test_handshake_eip8():
    cancel_token = CancelToken("test_handshake_eip8")
    use_eip8 = True
    initiator_remote = kademlia.Node(
        keys.PrivateKey(eip8_values['receiver_private_key']).public_key,
        kademlia.Address('0.0.0.0', 0, 0))
    initiator = HandshakeInitiator(
        initiator_remote,
        keys.PrivateKey(eip8_values['initiator_private_key']), use_eip8,
        cancel_token)
    initiator.ephemeral_privkey = keys.PrivateKey(
        eip8_values['initiator_ephemeral_private_key'])

    responder_remote = kademlia.Node(
        keys.PrivateKey(eip8_values['initiator_private_key']).public_key,
        kademlia.Address('0.0.0.0', 0, 0))
    responder = HandshakeResponder(
        responder_remote, keys.PrivateKey(eip8_values['receiver_private_key']),
        use_eip8, cancel_token)
    responder.ephemeral_privkey = keys.PrivateKey(
        eip8_values['receiver_ephemeral_private_key'])

    auth_init_ciphertext = eip8_values['auth_init_ciphertext']

    # Check that we can decrypt/decode the EIP-8 auth init message.
    initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication(
        auth_init_ciphertext, responder.privkey)
    assert initiator_nonce == eip8_values['initiator_nonce']
    assert initiator_ephemeral_pubkey == (keys.PrivateKey(
        eip8_values['initiator_ephemeral_private_key']).public_key)

    responder_nonce = eip8_values['receiver_nonce']
    auth_ack_ciphertext = eip8_values['auth_ack_ciphertext']
    aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets(
        initiator_nonce, responder_nonce, initiator_ephemeral_pubkey,
        auth_init_ciphertext, auth_ack_ciphertext)

    # Check that the secrets derived by the responder match the expected values.
    assert aes_secret == eip8_values['expected_aes_secret']
    assert mac_secret == eip8_values['expected_mac_secret']

    # Also according to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md, running B's
    # ingress-mac keccak state on the string "foo" yields the following hash:
    ingress_mac_copy = ingress_mac.copy()
    ingress_mac_copy.update(b'foo')
    assert ingress_mac_copy.hexdigest() == (
        '0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5')

    responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message(
        auth_ack_ciphertext)
    (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac,
     initiator_ingress_mac) = initiator.derive_secrets(
         initiator_nonce, responder_nonce, responder_ephemeral_pubkey,
         auth_init_ciphertext, auth_ack_ciphertext)

    # Check that the secrets derived by the initiator match the expected values.
    assert initiator_aes_secret == eip8_values['expected_aes_secret']
    assert initiator_mac_secret == eip8_values['expected_mac_secret']

    # Finally, check that two Peers configured with the secrets generated above understand each
    # other.
    (
        (responder_reader, responder_writer),
        (initiator_reader, initiator_writer),
    ) = get_directly_connected_streams()

    capabilities = (('testing', 1), )

    initiator_transport = Transport(remote=initiator_remote,
                                    private_key=initiator.privkey,
                                    reader=initiator_reader,
                                    writer=initiator_writer,
                                    aes_secret=initiator_aes_secret,
                                    mac_secret=initiator_mac_secret,
                                    egress_mac=initiator_egress_mac,
                                    ingress_mac=initiator_ingress_mac)
    initiator_p2p_protocol = P2PProtocol(initiator_transport, 0, False)
    initiator_multiplexer = Multiplexer(
        transport=initiator_transport,
        base_protocol=initiator_p2p_protocol,
        protocols=(),
    )
    initiator_multiplexer.get_base_protocol().send_handshake(
        'initiator',
        capabilities,
        30303,
        DEVP2P_V5,
    )

    responder_transport = Transport(
        remote=responder_remote,
        private_key=responder.privkey,
        reader=responder_reader,
        writer=responder_writer,
        aes_secret=aes_secret,
        mac_secret=mac_secret,
        egress_mac=egress_mac,
        ingress_mac=ingress_mac,
    )
    responder_p2p_protocol = P2PProtocolV4(responder_transport, 0, False)
    responder_multiplexer = Multiplexer(
        transport=responder_transport,
        base_protocol=responder_p2p_protocol,
        protocols=(),
    )
    responder_multiplexer.get_base_protocol().send_handshake(
        'responder',
        capabilities,
        30303,
        DEVP2P_V4,
    )

    async with initiator_multiplexer.multiplex():
        async with responder_multiplexer.multiplex():
            initiator_stream = initiator_multiplexer.stream_protocol_messages(
                initiator_p2p_protocol, )
            responder_stream = responder_multiplexer.stream_protocol_messages(
                responder_p2p_protocol, )

            initiator_hello, _ = await initiator_stream.asend(None)
            responder_hello, _ = await responder_stream.asend(None)

            await initiator_stream.aclose()
            await responder_stream.aclose()

    assert isinstance(responder_hello, Hello)
    assert isinstance(initiator_hello, Hello)