Example #1
0
def test_equality(identity_scheme_registry):
    base_kwargs = {
        "sequence_number": 0,
        "kv_pairs": {
            b"id": b"mock",
            b"key1": b"value1",
            b"key2": b"value2",
        },
        "signature": b"signature",
        "identity_scheme_registry": identity_scheme_registry,
    }

    base_enr = ENR(**base_kwargs)
    equal_enr = ENR(**base_kwargs)
    enr_different_sequence_number = ENR(
        **assoc(base_kwargs, "sequence_number", 1))
    enr_different_kv_pairs = ENR(
        **assoc_in(base_kwargs, ("kv_pairs", b"key1"), b"value2"), )
    enr_different_signature = ENR(
        **assoc(base_kwargs, "signature", b"different-signature"))

    assert base_enr == base_enr
    assert equal_enr == base_enr
    assert enr_different_sequence_number != base_enr
    assert enr_different_kv_pairs != base_enr
    assert enr_different_signature != base_enr
def test_auth_header_preparation(tag,
                                 auth_tag,
                                 id_nonce,
                                 initiator_key,
                                 auth_response_key,
                                 ephemeral_public_key):
    enr = ENR(
        sequence_number=1,
        signature=b"",
        kv_pairs={
            b"id": b"v4",
            b"secp256k1": b"\x02" * 33,
        }
    )
    message = PingMessage(
        request_id=5,
        enr_seq=enr.sequence_number,
    )
    id_nonce_signature = b"\x00" * 32

    packet = AuthHeaderPacket.prepare(
        tag=tag,
        auth_tag=auth_tag,
        id_nonce=id_nonce,
        message=message,
        initiator_key=initiator_key,
        id_nonce_signature=id_nonce_signature,
        auth_response_key=auth_response_key,
        enr=enr,
        ephemeral_public_key=ephemeral_public_key
    )

    assert packet.tag == tag
    assert packet.auth_header.auth_tag == auth_tag
    assert packet.auth_header.id_nonce == id_nonce
    assert packet.auth_header.auth_scheme_name == AUTH_SCHEME_NAME
    assert packet.auth_header.ephemeral_public_key == ephemeral_public_key

    decrypted_auth_response = aesgcm_decrypt(
        key=auth_response_key,
        nonce=ZERO_NONCE,
        cipher_text=packet.auth_header.encrypted_auth_response,
        authenticated_data=b"",
    )
    decoded_auth_response = rlp.decode(decrypted_auth_response)
    assert is_list_like(decoded_auth_response) and len(decoded_auth_response) == 3
    assert decoded_auth_response[0] == int_to_big_endian(AUTH_RESPONSE_VERSION)
    assert decoded_auth_response[1] == id_nonce_signature
    assert ENR.deserialize(decoded_auth_response[2]) == enr

    decrypted_message = aesgcm_decrypt(
        key=initiator_key,
        nonce=auth_tag,
        cipher_text=packet.encrypted_message,
        authenticated_data=tag,
    )
    assert decrypted_message[0] == message.message_type
    assert rlp.decode(decrypted_message[1:], PingMessage) == message
Example #3
0
def test_mapping_interface(identity_scheme_registry):
    kv_pairs = {
        b"id": b"mock",
        b"key1": b"value1",
        b"key2": b"value2",
    }
    enr = ENR(
        signature=b"",
        sequence_number=0,
        kv_pairs=kv_pairs,
        identity_scheme_registry=identity_scheme_registry,
    )

    for key, value in kv_pairs.items():
        assert key in enr
        assert enr[key] == value
        assert enr.get(key) == value

    not_a_key = b"key3"
    assert not_a_key not in kv_pairs
    assert not_a_key not in enr
    enr.get(not_a_key) is None
    assert enr.get(not_a_key, b"default") == b"default"

    assert tuple(enr.keys()) == tuple(kv_pairs.keys())
    assert tuple(enr.values()) == tuple(kv_pairs.values())
    assert tuple(enr.items()) == tuple(kv_pairs.items())

    assert len(enr) == len(kv_pairs)

    assert tuple(iter(enr)) == tuple(iter(kv_pairs))
Example #4
0
def test_signature_scheme_selection(mock_identity_scheme,
                                    identity_scheme_registry):
    mock_enr = ENR(0, {b"id": b"mock"}, b"", identity_scheme_registry)
    assert mock_enr.identity_scheme is mock_identity_scheme

    v4_enr = ENR(0, {
        b"id": b"v4",
        b"secp256k1": b"\x02" * 33
    }, b"", identity_scheme_registry)
    assert v4_enr.identity_scheme is V4IdentityScheme

    with pytest.raises(ValidationError):
        ENR(0, {b"id": b"other"}, b"", identity_scheme_registry)
Example #5
0
def test_extract_forkid():
    enr = ENR.from_repr(
        "enr:-Jq4QO5zEyIBU5lSa9iaen0A2xUB5_IVrCi1DbyASTTnLV5RJan6aGPr8kU0p0MYKU5YezZgdSUE"
        "-GOBEio6Ultyf1Aog2V0aMrJhGN2AZCDGfCggmlkgnY0gmlwhF4_wLuJc2VjcDI1NmsxoQOt7cA_B_Kg"
        "nQ5RmwyA6ji8M1Y0jfINItRGbOOwy7XgbIN0Y3CCdl-DdWRwgnZf")
    assert extract_forkid(enr) == ForkID(hash=to_bytes(hexstr='0x63760190'),
                                         next=1700000)
Example #6
0
def enr():
    return ENR(sequence_number=1,
               signature=b"",
               kv_pairs={
                   b"id":
                   b"v4",
                   b"secp256k1":
                   PrivateKey(b"\x01" * 32).public_key.to_compressed_bytes(),
               })
Example #7
0
def test_real_life_test_vector():
    enr = ENR.from_repr(REAL_LIFE_TEST_DATA["repr"])

    assert enr.sequence_number == REAL_LIFE_TEST_DATA["sequence_number"]
    assert enr.public_key == REAL_LIFE_TEST_DATA["public_key"]
    assert enr.node_id == REAL_LIFE_TEST_DATA["node_id"]
    assert enr.identity_scheme is REAL_LIFE_TEST_DATA["identity_scheme"]
    assert dict(enr) == REAL_LIFE_TEST_DATA["kv_pairs"]
    assert repr(enr) == REAL_LIFE_TEST_DATA["repr"]
    def decrypt_auth_response(
            self, auth_response_key: AES128Key) -> Tuple[bytes, Optional[ENR]]:
        """Extract id nonce signature and optional ENR from auth header packet."""
        plain_text = aesgcm_decrypt(
            key=auth_response_key,
            nonce=ZERO_NONCE,
            cipher_text=self.auth_header.encrypted_auth_response,
            authenticated_data=b"",
        )

        try:
            decoded_rlp = rlp.decode(plain_text)
        except DecodingError:
            raise ValidationError(
                f"Auth response does not contain valid RLP: {encode_hex(plain_text)}"
            )

        if not is_list_like(decoded_rlp):
            raise ValidationError(
                f"Auth response contains bytes instead of list: {encode_hex(decoded_rlp)}"
            )

        if len(decoded_rlp) != 3:
            raise ValidationError(
                f"Auth response is a list of {len(decoded_rlp)} instead of three elements"
            )
        version_bytes, id_nonce_signature, serialized_enr = decoded_rlp

        if not is_bytes(version_bytes):
            raise ValidationError(
                f"Version is a list instead of big endian encoded integer: {version_bytes}"
            )
        version_int = big_endian_to_int(version_bytes)
        if version_int != AUTH_RESPONSE_VERSION:
            raise ValidationError(
                f"Expected auth response version {AUTH_RESPONSE_VERSION}, but got {version_int}"
            )

        if not is_bytes(id_nonce_signature):
            raise ValidationError(
                f"Id nonce signature is a list instead of bytes: {id_nonce_signature}"
            )

        if not is_list_like(serialized_enr):
            raise ValidationError(
                f"ENR is bytes instead of list: {encode_hex(serialized_enr)}")

        if len(serialized_enr) == 0:
            enr = None
        else:
            try:
                enr = ENR.deserialize(serialized_enr)
            except DeserializationError as error:
                raise ValidationError(
                    "ENR in auth response is not properly encoded") from error

        return id_nonce_signature, enr
Example #9
0
def test_inititialization(identity_scheme_registry):
    valid_sequence_number = 0
    valid_kv_pairs = {b"id": b"mock"}
    valid_signature = b""  # signature is not validated during initialization

    assert UnsignedENR(
        sequence_number=valid_sequence_number,
        kv_pairs=valid_kv_pairs,
        identity_scheme_registry=identity_scheme_registry,
    )
    assert ENR(
        sequence_number=valid_sequence_number,
        kv_pairs=valid_kv_pairs,
        signature=valid_signature,
        identity_scheme_registry=identity_scheme_registry,
    )

    with pytest.raises(ValidationError):
        UnsignedENR(
            sequence_number=valid_sequence_number,
            kv_pairs={b"no-id": b""},
            identity_scheme_registry=identity_scheme_registry,
        )
    with pytest.raises(ValidationError):
        ENR(
            sequence_number=valid_sequence_number,
            kv_pairs={b"no-id": b""},
            signature=valid_signature,
            identity_scheme_registry=identity_scheme_registry,
        )

    with pytest.raises(ValidationError):
        UnsignedENR(
            sequence_number=-1,
            kv_pairs=valid_kv_pairs,
            identity_scheme_registry=identity_scheme_registry,
        )
    with pytest.raises(ValidationError):
        ENR(
            sequence_number=-1,
            kv_pairs=valid_kv_pairs,
            signature=valid_signature,
            identity_scheme_registry=identity_scheme_registry,
        )
Example #10
0
def test_repr(mock_identity_scheme, identity_scheme_registry):
    unsigned_enr = UnsignedENR(0, {b"id": b"mock"}, identity_scheme_registry)
    enr = unsigned_enr.to_signed_enr(b"\x00" * 32)
    base64_encoded_enr = base64.urlsafe_b64encode(rlp.encode(enr))
    represented_enr = repr(enr)

    assert represented_enr.startswith("enr:")
    assert base64_encoded_enr.rstrip(b"=").decode() == represented_enr[4:]

    assert ENR.from_repr(represented_enr, identity_scheme_registry) == enr
def test_enr_v4_compat_signature_validation():
    private_key = PrivateKey(b"\x11" * 32)
    enr = ENR(0, {
        b"id": b"v4-compat",
        b"secp256k1": private_key.public_key.to_compressed_bytes(),
        b"key1": b"value1",
    },
              signature=b'')

    V4CompatIdentityScheme.validate_enr_signature(enr)
Example #12
0
async def test_enr_response_handler_does_not_crash_on_invalid_responses():
    discovery = MockDiscoveryService([])
    token = b''
    invalid_enr = b'garbage'
    payload = [token, invalid_enr]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')

    enr = ENRFactory()
    enr._kv_pairs.pop(b'secp256k1')
    with pytest.raises(ValidationError):
        ENR.deserialize(ENR.serialize(enr))
    payload = [token, ENR.serialize(enr)]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')

    enr = ENRFactory()
    enr._signature = b'garbage'
    with pytest.raises(eth_keys.exceptions.ValidationError):
        enr.validate_signature()
    payload = [token, ENR.serialize(enr)]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')
Example #13
0
def create_stub_enr(pubkey: datatypes.PublicKey, address: AddressAPI) -> ENR:
    return ENR(
        0, {
            IDENTITY_SCHEME_ENR_KEY: V4CompatIdentityScheme.id,
            V4CompatIdentityScheme.public_key_enr_key:
            pubkey.to_compressed_bytes(),
            IP_V4_ADDRESS_ENR_KEY: address.ip_packed,
            UDP_PORT_ENR_KEY: address.udp_port,
            TCP_PORT_ENR_KEY: address.tcp_port,
        },
        signature=b'')
Example #14
0
def test_signature_validation(mock_identity_scheme, identity_scheme_registry):
    unsigned_enr = UnsignedENR(0, {b"id": b"mock"}, identity_scheme_registry)
    private_key = b"\x00" * 32
    enr = unsigned_enr.to_signed_enr(private_key)
    enr.validate_signature()

    invalid_signature = b"\xff" * 64
    invalid_enr = ENR(enr.sequence_number,
                      dict(enr),
                      invalid_signature,
                      identity_scheme_registry=identity_scheme_registry)
    with pytest.raises(ValidationError):
        invalid_enr.validate_signature()

    with pytest.raises(ValidationError):
        ENR(
            0,
            {b"id": b"unknown"},
            b"",
            identity_scheme_registry=identity_scheme_registry,
        )
Example #15
0
 def enrToMultiAddress(_enr):
     knode = KNode.from_enr_repr(_enr)
     return {
         "enode": knode.uri(),
         "enrdata": {
             "address": knode.address,
             "pubkey": knode.pubkey,
             "id": knode.id
         },
         "enritems": ENR.from_repr(_enr).items(),
         "multiaddr": Handler.enodeToMultiAddress(knode.uri())
     }
def test_enr_signature_validation():
    private_key = PrivateKey(b"\x11" * 32)
    unsigned_enr = UnsignedENR(
        0, {
            b"id": b"v4",
            b"secp256k1": private_key.public_key.to_compressed_bytes(),
            b"key1": b"value1",
        })
    enr = unsigned_enr.to_signed_enr(private_key.to_bytes())

    V4IdentityScheme.validate_enr_signature(enr)

    forged_enr = ENR(enr.sequence_number, dict(enr), b"\x00" * 64)
    with pytest.raises(ValidationError):
        V4IdentityScheme.validate_enr_signature(forged_enr)
Example #17
0
def test_official_test_vector():
    enr = ENR.from_repr(
        OFFICIAL_TEST_DATA["repr"])  # use default identity scheme registry

    assert enr.sequence_number == OFFICIAL_TEST_DATA["sequence_number"]
    assert dict(enr) == OFFICIAL_TEST_DATA["kv_pairs"]
    assert enr.public_key == OFFICIAL_TEST_DATA["public_key"]
    assert enr.node_id == OFFICIAL_TEST_DATA["node_id"]
    assert enr.identity_scheme is OFFICIAL_TEST_DATA["identity_scheme"]
    assert repr(enr) == OFFICIAL_TEST_DATA["repr"]

    unsigned_enr = UnsignedENR(enr.sequence_number, dict(enr))
    reconstructed_enr = unsigned_enr.to_signed_enr(
        OFFICIAL_TEST_DATA["private_key"])
    assert reconstructed_enr == enr
Example #18
0
 def _init(self, enr: ENR) -> None:
     try:
         ip = enr[IP_V4_ADDRESS_ENR_KEY]
         udp_port = enr[UDP_PORT_ENR_KEY]
     except KeyError:
         self._address = None
     else:
         tcp_port = enr.get(TCP_PORT_ENR_KEY, udp_port)
         self._address = Address(ip, udp_port, tcp_port)
     # FIXME: ENRs may use different pubkey formats and this would break, so instead of storing
     # a PublicKey with a certain format here we should simply use the APIs in the
     # ENR.identity_scheme for the crypto related operations.
     self._pubkey = keys.PublicKey.from_compressed_bytes(enr.public_key)
     self._id = NodeID(keccak(self.pubkey.to_bytes()))
     self._id_int = big_endian_to_int(self.id)
     self._enr = enr
Example #19
0
def test_serialization_roundtrip(identity_scheme_registry):
    original_enr = ENR(
        sequence_number=0,
        kv_pairs={
            b"id": b"mock",
            b"key2":
            b"value2",  # wrong order so that serialization is forced to fix this
            b"key1": b"value1",
        },
        signature=b"",
        identity_scheme_registry=identity_scheme_registry,
    )
    encoded = rlp.encode(original_enr)
    recovered_enr = rlp.decode(
        encoded,
        ENR,
        identity_scheme_registry=identity_scheme_registry,
    )
    assert recovered_enr == original_enr
Example #20
0
    async def do_run(self, event_bus: EndpointAPI) -> None:
        boot_info = self._boot_info
        identity_scheme_registry = default_identity_scheme_registry
        message_type_registry = default_message_type_registry

        nodedb_dir = get_nodedb_dir(boot_info)
        nodedb_dir.mkdir(exist_ok=True)
        node_db = NodeDB(default_identity_scheme_registry, LevelDB(nodedb_dir))

        local_private_key = get_local_private_key(boot_info)
        local_enr = await get_local_enr(boot_info, node_db, local_private_key)
        local_node_id = local_enr.node_id

        routing_table = KademliaRoutingTable(local_node_id,
                                             NUM_ROUTING_TABLE_BUCKETS)

        node_db.set_enr(local_enr)
        for enr_repr in boot_info.args.discovery_boot_enrs or ():
            enr = ENR.from_repr(enr_repr)
            node_db.set_enr(enr)
            routing_table.update(enr.node_id)

        port = boot_info.args.discovery_port

        socket = trio.socket.socket(family=trio.socket.AF_INET,
                                    type=trio.socket.SOCK_DGRAM)
        outgoing_datagram_channels = trio.open_memory_channel[
            OutgoingDatagram](0)
        incoming_datagram_channels = trio.open_memory_channel[
            IncomingDatagram](0)
        outgoing_packet_channels = trio.open_memory_channel[OutgoingPacket](0)
        incoming_packet_channels = trio.open_memory_channel[IncomingPacket](0)
        outgoing_message_channels = trio.open_memory_channel[OutgoingMessage](
            0)
        incoming_message_channels = trio.open_memory_channel[IncomingMessage](
            0)
        endpoint_vote_channels = trio.open_memory_channel[EndpointVote](0)

        # types ignored due to https://github.com/ethereum/async-service/issues/5
        datagram_sender = DatagramSender(  # type: ignore
            outgoing_datagram_channels[1], socket)
        datagram_receiver = DatagramReceiver(  # type: ignore
            socket, incoming_datagram_channels[0])

        packet_encoder = PacketEncoder(  # type: ignore
            outgoing_packet_channels[1], outgoing_datagram_channels[0])
        packet_decoder = PacketDecoder(  # type: ignore
            incoming_datagram_channels[1], incoming_packet_channels[0])

        packer = Packer(
            local_private_key=local_private_key.to_bytes(),
            local_node_id=local_node_id,
            node_db=node_db,
            message_type_registry=message_type_registry,
            incoming_packet_receive_channel=incoming_packet_channels[1],
            incoming_message_send_channel=incoming_message_channels[0],
            outgoing_message_receive_channel=outgoing_message_channels[1],
            outgoing_packet_send_channel=outgoing_packet_channels[0],
        )

        message_dispatcher = MessageDispatcher(
            node_db=node_db,
            incoming_message_receive_channel=incoming_message_channels[1],
            outgoing_message_send_channel=outgoing_message_channels[0],
        )

        endpoint_tracker = EndpointTracker(
            local_private_key=local_private_key.to_bytes(),
            local_node_id=local_node_id,
            node_db=node_db,
            identity_scheme_registry=identity_scheme_registry,
            vote_receive_channel=endpoint_vote_channels[1],
        )

        routing_table_manager = RoutingTableManager(
            local_node_id=local_node_id,
            routing_table=routing_table,
            message_dispatcher=message_dispatcher,
            node_db=node_db,
            outgoing_message_send_channel=outgoing_message_channels[0],
            endpoint_vote_send_channel=endpoint_vote_channels[0],
        )

        logger.info(f"Starting discovery, listening on port {port}")
        logger.info(f"Local Node ID: {encode_hex(local_enr.node_id)}")
        logger.info(f"Local ENR: {local_enr}")

        services = (
            datagram_sender,
            datagram_receiver,
            packet_encoder,
            packet_decoder,
            packer,
            message_dispatcher,
            endpoint_tracker,
            routing_table_manager,
        )
        await socket.bind(("0.0.0.0", port))
        with socket:
            async with trio.open_nursery() as nursery:
                for service in services:
                    nursery.start_soon(async_service.TrioManager.run_service,
                                       service)
Example #21
0
         "request_id": 0x01,
         "total": 0x01,
         "enrs": [],
     },
     decode_hex("0x04c30101c0"),
 ],
 [
     NodesMessage,
     {
         "request_id":
         0x01,
         "total":
         0x01,
         "enrs": [
             ENR.from_repr(
                 "enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxa"
                 "agKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAP"
                 "MljNMTg"),
             ENR.from_repr(
                 "enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_"
                 "KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8x"
                 "fVw50jU"),
         ]
     },
     decode_hex(
         "0x04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc6"
         "55448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa532801826964827634897365"
         "63703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875"
         "b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b44"
         "5946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e"
         "2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"),
 ],
Example #22
0
 def from_enr_repr(cls: Type[TNode], uri: str) -> TNode:
     return cls(ENR.from_repr(uri))
Example #23
0
 def __setstate__(self, state: Dict[Any, Any]) -> None:
     self._init(ENR.from_repr(state.pop('enr')))