예제 #1
0
async def get_listening_discovery_protocol(event_loop):
    addr = kademlia.Address("127.0.0.1", random.randint(1024, 9999))
    proto = get_discovery_protocol(os.urandom(4), addr)
    await event_loop.create_datagram_endpoint(
        lambda: proto, local_addr=(addr.ip, addr.udp_port), family=socket.AF_INET
    )
    return proto
예제 #2
0
def test_check_relayed_addr():
    public_host = kademlia.Address("8.8.8.8", 80)
    local_host = kademlia.Address("127.0.0.1", 80)
    assert kademlia.check_relayed_addr(local_host, local_host)
    assert not kademlia.check_relayed_addr(public_host, local_host)

    private = kademlia.Address("192.168.1.1", 80)
    assert kademlia.check_relayed_addr(private, private)
    assert not kademlia.check_relayed_addr(public_host, private)

    reserved = kademlia.Address("240.0.0.1", 80)
    assert not kademlia.check_relayed_addr(local_host, reserved)
    assert not kademlia.check_relayed_addr(public_host, reserved)

    unspecified = kademlia.Address("0.0.0.0", 80)
    assert not kademlia.check_relayed_addr(local_host, unspecified)
    assert not kademlia.check_relayed_addr(public_host, unspecified)
예제 #3
0
def main():
    Logger.set_logging_level("debug")
    loop = asyncio.get_event_loop()
    loop.set_debug(True)

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--bootnode",
        default="enode://c571e0db93d17cc405cb57640826b70588a6a28785f38b21be471c609ca12fcb06cb306ac44872908f5bed99046031a5af82072d484e3ef9029560c1707193a0@127.0.0.1:29000",
        type=str,
    )
    parser.add_argument("--listen_host", default="127.0.0.1", type=str)
    parser.add_argument(
        "--listen_port",
        default=29000,
        help="port for discovery UDP and P2P TCP connection",
        type=int,
    )
    parser.add_argument("--max_peers", default=10, type=int)
    # private key of the bootnode above is 31552f186bf90908ce386fb547dd0410bf443309125cc43fd0ffd642959bf6d9
    parser.add_argument(
        "--privkey",
        default="",
        help="hex string of private key; if empty, will be auto-generated",
        type=str,
    )

    args = parser.parse_args()

    if args.privkey:
        privkey = keys.PrivateKey(bytes.fromhex(args.privkey))
    else:
        privkey = ecies.generate_privkey()
    addr = kademlia.Address(args.listen_host, args.listen_port, args.listen_port)
    bootstrap_nodes = tuple([kademlia.Node.from_uri(args.bootnode)])

    cancel_token = CancelToken("discovery")
    discovery = DiscoveryProtocol(privkey, addr, bootstrap_nodes, cancel_token)

    async def run() -> None:
        await loop.create_datagram_endpoint(
            lambda: discovery, local_addr=("0.0.0.0", args.listen_port)
        )
        try:
            await discovery.bootstrap()
            while True:
                Logger.info("Routing table size={}".format(len(discovery.routing)))
                await cancel_token.cancellable_wait(asyncio.sleep(5))
        except OperationCancelled:
            pass
        finally:
            await discovery.stop()

    for sig in [signal.SIGINT, signal.SIGTERM]:
        loop.add_signal_handler(sig, cancel_token.trigger)

    loop.run_until_complete(run())
    loop.close()
예제 #4
0
def random_address():
    return kademlia.Address("10.0.0.{}".format(random.randint(0, 255)),
                            random.randint(0, 9999))
예제 #5
0
async def get_directly_linked_peers_without_handshake(
    alice_factory: BasePeerFactory = None, bob_factory: BasePeerFactory = None
) -> Tuple[BasePeer, BasePeer]:
    """
    See get_directly_linked_peers().

    Neither the P2P handshake nor the sub-protocol handshake will be performed here.
    """
    cancel_token = CancelToken("get_directly_linked_peers_without_handshake")

    if alice_factory is None:
        alice_factory = ParagonPeerFactory(
            privkey=ecies.generate_privkey(),
            context=ParagonContext(),
            token=cancel_token,
            listen_port=30303,
        )

    if bob_factory is None:
        bob_factory = ParagonPeerFactory(
            privkey=ecies.generate_privkey(),
            context=ParagonContext(),
            token=cancel_token,
            listen_port=30303,
        )

    alice_private_key = alice_factory.privkey
    bob_private_key = bob_factory.privkey

    alice_remote = kademlia.Node(
        bob_private_key.public_key, kademlia.Address("0.0.0.0", 0, 0)
    )
    bob_remote = kademlia.Node(
        alice_private_key.public_key, kademlia.Address("0.0.0.0", 0, 0)
    )

    use_eip8 = False
    initiator = auth.HandshakeInitiator(
        alice_remote, alice_private_key, use_eip8, cancel_token
    )

    f_alice = asyncio.Future()  # : 'asyncio.Future[BasePeer]'
    handshake_finished = asyncio.Event()

    (
        (alice_reader, alice_writer),
        (bob_reader, bob_writer),
    ) = get_directly_connected_streams()

    async def do_handshake() -> None:
        aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake(
            initiator, alice_reader, alice_writer, cancel_token
        )

        connection = PeerConnection(
            reader=alice_reader,
            writer=alice_writer,
            aes_secret=aes_secret,
            mac_secret=mac_secret,
            egress_mac=egress_mac,
            ingress_mac=ingress_mac,
        )
        alice = alice_factory.create_peer(alice_remote, connection)

        f_alice.set_result(alice)
        handshake_finished.set()

    asyncio.ensure_future(do_handshake())

    use_eip8 = False
    responder = auth.HandshakeResponder(
        bob_remote, bob_private_key, use_eip8, cancel_token
    )
    auth_cipher = await bob_reader.read(constants.ENCRYPTED_AUTH_MSG_LEN)

    initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication(
        auth_cipher, bob_private_key
    )
    responder_nonce = keccak(os.urandom(constants.HASH_LEN))
    auth_ack_msg = responder.create_auth_ack_message(responder_nonce)
    auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg)
    bob_writer.write(auth_ack_ciphertext)

    await handshake_finished.wait()
    alice = await f_alice

    aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets(
        initiator_nonce,
        responder_nonce,
        initiator_ephemeral_pubkey,
        auth_cipher,
        auth_ack_ciphertext,
    )
    assert egress_mac.digest() == alice.ingress_mac.digest()
    assert ingress_mac.digest() == alice.egress_mac.digest()
    connection = PeerConnection(
        reader=bob_reader,
        writer=bob_writer,
        aes_secret=aes_secret,
        mac_secret=mac_secret,
        egress_mac=egress_mac,
        ingress_mac=ingress_mac,
    )
    bob = bob_factory.create_peer(bob_remote, connection)

    return alice, bob
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()

    initiator_connection = PeerConnection(
        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_peer = ParagonPeer(
        remote=initiator.remote,
        privkey=initiator.privkey,
        connection=initiator_connection,
        context=ParagonContext(),
    )
    initiator_peer.base_protocol.send_handshake()
    responder_connection = PeerConnection(
        reader=responder_reader,
        writer=responder_writer,
        aes_secret=aes_secret,
        mac_secret=mac_secret,
        egress_mac=egress_mac,
        ingress_mac=ingress_mac,
    )
    responder_peer = ParagonPeer(
        remote=responder.remote,
        privkey=responder.privkey,
        connection=responder_connection,
        context=ParagonContext(),
    )
    responder_peer.base_protocol.send_handshake()

    # The handshake msgs sent by each peer (above) are going to be fed directly into their remote's
    # reader, and thus the read_msg() calls will return immediately.
    responder_hello, _ = await responder_peer.read_msg()
    initiator_hello, _ = await initiator_peer.read_msg()

    assert isinstance(responder_hello, Hello)
    assert isinstance(initiator_hello, Hello)
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.digest().hex() == (
        "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()

    initiator_connection = PeerConnection(
        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_peer = ParagonPeer(
        remote=initiator.remote,
        privkey=initiator.privkey,
        connection=initiator_connection,
        context=ParagonContext(),
    )
    initiator_peer.base_protocol.send_handshake()
    responder_connection = PeerConnection(
        reader=responder_reader,
        writer=responder_writer,
        aes_secret=aes_secret,
        mac_secret=mac_secret,
        egress_mac=egress_mac,
        ingress_mac=ingress_mac,
    )
    responder_peer = ParagonPeer(
        remote=responder.remote,
        privkey=responder.privkey,
        connection=responder_connection,
        context=ParagonContext(),
    )
    responder_peer.base_protocol.send_handshake()

    # The handshake msgs sent by each peer (above) are going to be fed directly into their remote's
    # reader, and thus the read_msg() calls will return immediately.
    responder_hello, _ = await responder_peer.read_msg()
    initiator_hello, _ = await initiator_peer.read_msg()

    assert isinstance(responder_hello, Hello)
    assert isinstance(initiator_hello, Hello)
예제 #8
0
def random_node(nodeid=None):
    address = kademlia.Address("127.0.0.1", 30303)
    node = kademlia.Node(random_pubkey(), address)
    if nodeid is not None:
        node.id = nodeid
    return node