"""Tests for KeysAndCert and Destination.""" import base64 import hashlib import os class TestKeysAndCert: def _make_null_kac(self): """Create a KeysAndCert with NULL certificate and ElGamal/DSA keys.""" 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 pub, sig, cert def _make_key_cert_kac(self): """Create a KeysAndCert with KEY certificate and EdDSA/X25519.""" import struct from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import KeyCertificate from i2p_crypto.dsa import SigType pub = PublicKey(os.urandom(32), EncType.ECIES_X25519) sig = SigningPublicKey(os.urandom(32), SigType.EdDSA_SHA512_Ed25519) # KEY cert: SigType(7) + EncType(4) = 4 bytes payload payload = struct.pack("!HH", 7, 4) cert = KeyCertificate(payload) return pub, sig, cert def test_construct_null_cert(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_null_kac() kac = KeysAndCert(pub, sig, cert) assert kac.public_key is pub assert kac.signing_public_key is sig assert kac.certificate is cert def test_serialize_null_cert_size(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_null_kac() kac = KeysAndCert(pub, sig, cert) data = kac.to_bytes() assert len(data) == 256 + 128 + 3 # 387 def test_roundtrip_null_cert(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_null_kac() kac = KeysAndCert(pub, sig, cert) data = kac.to_bytes() kac2 = KeysAndCert.from_bytes(data) assert kac2.public_key == pub assert kac2.signing_public_key == sig assert kac2.to_bytes() == data def test_roundtrip_key_cert(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_key_cert_kac() kac = KeysAndCert(pub, sig, cert) data = kac.to_bytes() # With KEY cert: 256 + 128 + 3(header) + 4(payload) = 391 assert len(data) == 391 kac2 = KeysAndCert.from_bytes(data) assert kac2.public_key == pub assert kac2.signing_public_key == sig def test_hash_is_sha256(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_null_kac() kac = KeysAndCert(pub, sig, cert) expected = hashlib.sha256(kac.to_bytes()).digest() assert kac.hash() == expected assert len(kac.hash()) == 32 def test_hash_cached(self): from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_null_kac() kac = KeysAndCert(pub, sig, cert) h1 = kac.hash() h2 = kac.hash() assert h1 is h2 # Same object, cached def test_equality(self): from i2p_data.keys_and_cert import KeysAndCert from i2p_data.key_types import PublicKey, SigningPublicKey, EncType from i2p_data.certificate import Certificate from i2p_crypto.dsa import SigType data_pub = os.urandom(256) data_sig = os.urandom(128) kac1 = KeysAndCert( PublicKey(data_pub, EncType.ELGAMAL), SigningPublicKey(data_sig, SigType.DSA_SHA1), Certificate.NULL ) kac2 = KeysAndCert( PublicKey(data_pub, EncType.ELGAMAL), SigningPublicKey(data_sig, SigType.DSA_SHA1), Certificate.NULL ) assert kac1 == kac2 assert hash(kac1) == hash(kac2) def test_inequality(self): from i2p_data.keys_and_cert import KeysAndCert pub1, sig1, cert1 = self._make_null_kac() pub2, sig2, cert2 = self._make_null_kac() kac1 = KeysAndCert(pub1, sig1, cert1) kac2 = KeysAndCert(pub2, sig2, cert2) assert kac1 != kac2 def test_from_bytes_too_short(self): from i2p_data.keys_and_cert import KeysAndCert import pytest with pytest.raises(ValueError): KeysAndCert.from_bytes(b"\x00" * 100) def test_key_cert_right_aligned(self): """With X25519 (32 bytes), the key should be right-aligned in 256-byte area.""" from i2p_data.keys_and_cert import KeysAndCert pub, sig, cert = self._make_key_cert_kac() kac = KeysAndCert(pub, sig, cert) data = kac.to_bytes() # Public key area: 224 zero bytes + 32 bytes of key assert data[:224] == b"\x00" * 224 assert data[224:256] == pub.to_bytes() class TestDestination: def _make_destination(self): from i2p_data.destination import Destination 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 Destination(pub, sig, cert) def test_to_base64_roundtrip(self): from i2p_data.destination import Destination dest = self._make_destination() b64 = dest.to_base64() assert isinstance(b64, str) dest2 = Destination.from_base64(b64) assert dest2.to_bytes() == dest.to_bytes() def test_base64_no_padding(self): dest = self._make_destination() b64 = dest.to_base64() assert "=" not in b64 def test_to_base32_format(self): dest = self._make_destination() b32 = dest.to_base32() assert b32.endswith(".b32.i2p") # SHA-256 = 32 bytes = 52 base32 chars addr_part = b32.replace(".b32.i2p", "") assert len(addr_part) == 52 def test_base32_is_sha256(self): dest = self._make_destination() b32 = dest.to_base32() addr_part = b32.replace(".b32.i2p", "") expected_hash = hashlib.sha256(dest.to_bytes()).digest() expected_b32 = base64.b32encode(expected_hash).rstrip(b"=").decode("ascii").lower() assert addr_part == expected_b32 def test_base32_cached(self): dest = self._make_destination() b32a = dest.to_base32() b32b = dest.to_base32() assert b32a is b32b # Cached def test_from_bytes_roundtrip(self): from i2p_data.destination import Destination dest = self._make_destination() data = dest.to_bytes() dest2 = Destination.from_bytes(data) assert dest2.to_bytes() == data assert dest2.to_base32() == dest.to_base32() def test_different_destinations_different_b32(self): dest1 = self._make_destination() dest2 = self._make_destination() assert dest1.to_base32() != dest2.to_base32() def test_inherits_keys_and_cert(self): from i2p_data.keys_and_cert import KeysAndCert dest = self._make_destination() assert isinstance(dest, KeysAndCert) assert len(dest.hash()) == 32