"""Tests for RouterIdentity, RouterAddress, RouterInfo.""" import os import struct class TestRouterAddress: def test_construct(self): from i2p_data.router import RouterAddress addr = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4", "port": "15555"}) assert addr.cost == 10 assert addr.expiration == 0 assert addr.transport == "NTCP2" assert addr.get_host() == "1.2.3.4" assert addr.get_port() == 15555 def test_roundtrip(self): from i2p_data.router import RouterAddress addr = RouterAddress(5, 0, "SSU2", {"host": "::1", "port": "9999", "key": "abc123"}) data = addr.to_bytes() addr2, consumed = RouterAddress.from_bytes(data) assert consumed == len(data) assert addr2.cost == 5 assert addr2.transport == "SSU2" assert addr2.get_host() == "::1" assert addr2.get_port() == 9999 assert addr2.options["key"] == "abc123" def test_no_options(self): from i2p_data.router import RouterAddress addr = RouterAddress(20, 0, "NTCP2") data = addr.to_bytes() addr2, _ = RouterAddress.from_bytes(data) assert addr2.transport == "NTCP2" assert addr2.options == {} assert addr2.get_host() is None assert addr2.get_port() is None def test_equality(self): from i2p_data.router import RouterAddress a1 = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4"}) a2 = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4"}) a3 = RouterAddress(20, 0, "NTCP2", {"host": "1.2.3.4"}) assert a1 == a2 assert a1 != a3 def test_multiple_addresses_roundtrip(self): from i2p_data.router import RouterAddress import io addrs = [ RouterAddress(5, 0, "NTCP2", {"host": "10.0.0.1", "port": "443"}), RouterAddress(10, 0, "SSU2", {"host": "10.0.0.1", "port": "9999"}), ] buf = io.BytesIO() for a in addrs: buf.write(a.to_bytes()) buf.seek(0) recovered = [] for _ in range(2): a, _ = RouterAddress.from_stream(buf) recovered.append(a) assert recovered[0].transport == "NTCP2" assert recovered[1].transport == "SSU2" class TestRouterIdentity: def _make_router_identity(self): from i2p_data.router import RouterIdentity from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import Certificate from i2p_crypto.dsa import SigType pub = PublicKey(os.urandom(256), EncType.ELGAMAL) sig = SigningPublicKey(os.urandom(128), SigType.DSA_SHA1) cert = Certificate.NULL return RouterIdentity(pub, sig, cert) def test_construct(self): from i2p_data.keys_and_cert import KeysAndCert ri = self._make_router_identity() assert isinstance(ri, KeysAndCert) assert len(ri.to_bytes()) == 387 def test_roundtrip(self): from i2p_data.router import RouterIdentity ri = self._make_router_identity() data = ri.to_bytes() ri2 = RouterIdentity.from_bytes(data) assert ri2.to_bytes() == data assert ri2.public_key == ri.public_key class TestRouterInfo: def _make_signed_router_info(self): """Create a RouterInfo with real EdDSA signature.""" from i2p_data.router import RouterIdentity, RouterAddress, RouterInfo from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import KeyCertificate from i2p_crypto.dsa import SigType, KeyGenerator # Generate EdDSA keypair pub_sig, priv_sig = KeyGenerator.generate(SigType.EdDSA_SHA512_Ed25519) # Create identity with KEY cert pub_enc = PublicKey(os.urandom(32), EncType.ECIES_X25519) sig_key = SigningPublicKey(pub_sig, SigType.EdDSA_SHA512_Ed25519) cert = KeyCertificate(struct.pack("!HH", 7, 4)) # EdDSA + X25519 identity = RouterIdentity(pub_enc, sig_key, cert) addresses = [ RouterAddress(10, 0, "NTCP2", {"host": "192.168.1.1", "port": "15555"}), ] options = {"router.version": "0.9.62", "caps": "XfR"} info = RouterInfo(identity, 1710000000000, addresses, options) info.sign(priv_sig) return info, priv_sig def test_sign_and_verify(self): info, _ = self._make_signed_router_info() assert info.verify() def test_verify_fails_with_modified_data(self): from i2p_data.router import RouterInfo info, _ = self._make_signed_router_info() # Modify options after signing data = info.to_bytes() # Tamper with a byte in the middle tampered = bytearray(data) tampered[200] ^= 0xFF info2 = RouterInfo.from_bytes(bytes(tampered)) assert not info2.verify() def test_roundtrip(self): from i2p_data.router import RouterInfo info, _ = self._make_signed_router_info() data = info.to_bytes() info2 = RouterInfo.from_bytes(data) assert info2.identity.to_bytes() == info.identity.to_bytes() assert info2.published == info.published assert len(info2.addresses) == 1 assert info2.addresses[0].get_host() == "192.168.1.1" assert info2.options["router.version"] == "0.9.62" assert info2.verify() def test_no_addresses(self): from i2p_data.router import RouterIdentity, RouterInfo from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import KeyCertificate from i2p_crypto.dsa import SigType, KeyGenerator pub_sig, priv_sig = KeyGenerator.generate(SigType.EdDSA_SHA512_Ed25519) identity = RouterIdentity( PublicKey(os.urandom(32), EncType.ECIES_X25519), SigningPublicKey(pub_sig, SigType.EdDSA_SHA512_Ed25519), KeyCertificate(struct.pack("!HH", 7, 4)) ) info = RouterInfo(identity, 1710000000000) info.sign(priv_sig) assert info.verify() assert len(info.addresses) == 0 def test_unsigned_verify_fails(self): from i2p_data.router import RouterIdentity, RouterInfo from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import Certificate from i2p_crypto.dsa import SigType identity = RouterIdentity( PublicKey(os.urandom(256), EncType.ELGAMAL), SigningPublicKey(os.urandom(128), SigType.DSA_SHA1), Certificate.NULL ) info = RouterInfo(identity, 1710000000000) assert not info.verify()