"""Tests for the canonical public_key_fingerprint function. All public key fingerprints in Muse must use the sha256: prefix — the same convention as every other content-addressed value. These tests drive the single canonical implementation in muse.core.types. """ from __future__ import annotations import pathlib import pytest import hashlib from muse.core.types import split_id class TestPublicKeyFingerprintCanonical: """The canonical function lives in muse.core.types.""" def test_returns_sha256_prefixed_string(self) -> None: from muse.core.types import public_key_fingerprint fp = public_key_fingerprint(b"\x00" * 32) assert fp.startswith("sha256:") def test_hex_portion_is_64_chars(self) -> None: from muse.core.types import public_key_fingerprint fp = public_key_fingerprint(b"\x00" * 32) _, hex_part = split_id(fp) assert len(hex_part) == 64 def test_hex_portion_is_lowercase_hex(self) -> None: from muse.core.types import public_key_fingerprint fp = public_key_fingerprint(b"\xff" * 32) _, hex_part = split_id(fp) assert all(c in "0123456789abcdef" for c in hex_part) def test_correct_sha256_of_input(self) -> None: from muse.core.types import public_key_fingerprint data = b"test public key bytes" expected = "sha256:" + hashlib.sha256(data).hexdigest() assert public_key_fingerprint(data) == expected def test_known_zero_key(self) -> None: from muse.core.types import public_key_fingerprint data = b"\x00" * 32 expected = "sha256:" + hashlib.sha256(data).hexdigest() assert public_key_fingerprint(data) == expected def test_deterministic(self) -> None: from muse.core.types import public_key_fingerprint data = b"deterministic input" assert public_key_fingerprint(data) == public_key_fingerprint(data) def test_different_inputs_produce_different_fingerprints(self) -> None: from muse.core.types import public_key_fingerprint assert public_key_fingerprint(b"aaa") != public_key_fingerprint(b"bbb") def test_empty_bytes(self) -> None: from muse.core.types import public_key_fingerprint fp = public_key_fingerprint(b"") assert fp == "sha256:" + hashlib.sha256(b"").hexdigest() def test_total_length_is_71(self) -> None: # "sha256:" (7) + 64 hex chars = 71 from muse.core.types import public_key_fingerprint assert len(public_key_fingerprint(b"x" * 32)) == 71 class TestPublicKeyFingerprintKeypairModule: """keypair.py::public_key_fingerprint must delegate to _types and return prefixed value.""" def test_returns_sha256_prefixed_string(self) -> None: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from muse.core.keypair import public_key_fingerprint private_key = Ed25519PrivateKey.generate() public_key = private_key.public_key() fp = public_key_fingerprint(public_key) assert fp.startswith("sha256:") def test_hex_portion_is_64_chars(self) -> None: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from muse.core.keypair import public_key_fingerprint private_key = Ed25519PrivateKey.generate() fp = public_key_fingerprint(private_key.public_key()) _, hex_part = split_id(fp) assert len(hex_part) == 64 def test_consistent_with_types_module(self) -> None: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from muse.core.types import public_key_fingerprint as canonical_fp from muse.core.keypair import public_key_fingerprint as keypair_fp private_key = Ed25519PrivateKey.generate() public_key = private_key.public_key() raw = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw) assert keypair_fp(public_key) == canonical_fp(raw) class TestNoBareFingerprintsAnywhere: """The old bare-hex implementations must no longer exist.""" def test_agent_fingerprint_function_is_gone(self) -> None: import importlib import inspect mod = importlib.import_module("muse.cli.commands.agent") # _fingerprint should not exist as a standalone function anymore assert not hasattr(mod, "_fingerprint"), ( "_fingerprint still exists in agent.py — delete it and route callers " "through muse.core.types.public_key_fingerprint" ) def test_provenance_fingerprint_function_is_gone(self) -> None: import importlib mod = importlib.import_module("muse.core.provenance") assert not hasattr(mod, "public_key_fingerprint"), ( "public_key_fingerprint still exists in provenance.py — delete it and " "route callers through muse.core.types.public_key_fingerprint" ) class TestFingerprintInIdentityEntry: """Fingerprints written to identity.toml must carry the sha256: prefix.""" def test_derive_hd_public_info_returns_prefixed_fingerprint(self) -> None: from muse.core.keypair import derive_hd_public_info seed = b"\x00" * 64 _, fingerprint = derive_hd_public_info(seed) assert fingerprint.startswith("sha256:"), ( f"derive_hd_public_info returned bare fingerprint {fingerprint!r} — " "must be sha256:-prefixed" ) def test_fingerprint_field_in_identity_entry_is_prefixed(self, tmp_path: pathlib.Path) -> None: """Round-trip: save an identity, load it back, fingerprint has prefix.""" from muse.core.identity import save_identity, load_identity from muse.core.types import public_key_fingerprint import secrets fingerprint = public_key_fingerprint(b"fake-public-key-bytes") entry = { "type": "human", "handle": "gabriel", "key_path": str(tmp_path / "key.pem"), "algorithm": "ed25519", "fingerprint": fingerprint, } # Monkeypatch the identity file location import muse.core.identity as id_mod orig = id_mod._IDENTITY_FILE id_mod._IDENTITY_FILE = tmp_path / "identity.toml" try: save_identity("localhost:1337", entry) # type: ignore[arg-type] loaded = load_identity("localhost:1337") assert loaded is not None assert loaded.get("fingerprint", "").startswith("sha256:"), ( f"Loaded fingerprint {loaded.get('fingerprint')!r} lacks sha256: prefix" ) finally: id_mod._IDENTITY_FILE = orig