gabriel / muse public
test_public_key_fingerprint.py python
159 lines 6.6 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
1 """Tests for the canonical public_key_fingerprint function.
2
3 All public key fingerprints in Muse must use the sha256: prefix — the same
4 convention as every other content-addressed value. These tests drive the
5 single canonical implementation in muse.core.types.
6 """
7 from __future__ import annotations
8
9 import pathlib
10 import pytest
11
12 import hashlib
13
14 from muse.core.types import split_id
15
16
17 class TestPublicKeyFingerprintCanonical:
18 """The canonical function lives in muse.core.types."""
19
20 def test_returns_sha256_prefixed_string(self) -> None:
21 from muse.core.types import public_key_fingerprint
22 fp = public_key_fingerprint(b"\x00" * 32)
23 assert fp.startswith("sha256:")
24
25 def test_hex_portion_is_64_chars(self) -> None:
26 from muse.core.types import public_key_fingerprint
27 fp = public_key_fingerprint(b"\x00" * 32)
28 _, hex_part = split_id(fp)
29 assert len(hex_part) == 64
30
31 def test_hex_portion_is_lowercase_hex(self) -> None:
32 from muse.core.types import public_key_fingerprint
33 fp = public_key_fingerprint(b"\xff" * 32)
34 _, hex_part = split_id(fp)
35 assert all(c in "0123456789abcdef" for c in hex_part)
36
37 def test_correct_sha256_of_input(self) -> None:
38 from muse.core.types import public_key_fingerprint
39 data = b"test public key bytes"
40 expected = "sha256:" + hashlib.sha256(data).hexdigest()
41 assert public_key_fingerprint(data) == expected
42
43 def test_known_zero_key(self) -> None:
44 from muse.core.types import public_key_fingerprint
45 data = b"\x00" * 32
46 expected = "sha256:" + hashlib.sha256(data).hexdigest()
47 assert public_key_fingerprint(data) == expected
48
49 def test_deterministic(self) -> None:
50 from muse.core.types import public_key_fingerprint
51 data = b"deterministic input"
52 assert public_key_fingerprint(data) == public_key_fingerprint(data)
53
54 def test_different_inputs_produce_different_fingerprints(self) -> None:
55 from muse.core.types import public_key_fingerprint
56 assert public_key_fingerprint(b"aaa") != public_key_fingerprint(b"bbb")
57
58 def test_empty_bytes(self) -> None:
59 from muse.core.types import public_key_fingerprint
60 fp = public_key_fingerprint(b"")
61 assert fp == "sha256:" + hashlib.sha256(b"").hexdigest()
62
63 def test_total_length_is_71(self) -> None:
64 # "sha256:" (7) + 64 hex chars = 71
65 from muse.core.types import public_key_fingerprint
66 assert len(public_key_fingerprint(b"x" * 32)) == 71
67
68
69 class TestPublicKeyFingerprintKeypairModule:
70 """keypair.py::public_key_fingerprint must delegate to _types and return prefixed value."""
71
72 def test_returns_sha256_prefixed_string(self) -> None:
73 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
74 from muse.core.keypair import public_key_fingerprint
75 private_key = Ed25519PrivateKey.generate()
76 public_key = private_key.public_key()
77 fp = public_key_fingerprint(public_key)
78 assert fp.startswith("sha256:")
79
80 def test_hex_portion_is_64_chars(self) -> None:
81 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
82 from muse.core.keypair import public_key_fingerprint
83 private_key = Ed25519PrivateKey.generate()
84 fp = public_key_fingerprint(private_key.public_key())
85 _, hex_part = split_id(fp)
86 assert len(hex_part) == 64
87
88 def test_consistent_with_types_module(self) -> None:
89 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
90 from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
91 from muse.core.types import public_key_fingerprint as canonical_fp
92 from muse.core.keypair import public_key_fingerprint as keypair_fp
93 private_key = Ed25519PrivateKey.generate()
94 public_key = private_key.public_key()
95 raw = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
96 assert keypair_fp(public_key) == canonical_fp(raw)
97
98
99 class TestNoBareFingerprintsAnywhere:
100 """The old bare-hex implementations must no longer exist."""
101
102 def test_agent_fingerprint_function_is_gone(self) -> None:
103 import importlib
104 import inspect
105 mod = importlib.import_module("muse.cli.commands.agent")
106 # _fingerprint should not exist as a standalone function anymore
107 assert not hasattr(mod, "_fingerprint"), (
108 "_fingerprint still exists in agent.py — delete it and route callers "
109 "through muse.core.types.public_key_fingerprint"
110 )
111
112 def test_provenance_fingerprint_function_is_gone(self) -> None:
113 import importlib
114 mod = importlib.import_module("muse.core.provenance")
115 assert not hasattr(mod, "public_key_fingerprint"), (
116 "public_key_fingerprint still exists in provenance.py — delete it and "
117 "route callers through muse.core.types.public_key_fingerprint"
118 )
119
120
121 class TestFingerprintInIdentityEntry:
122 """Fingerprints written to identity.toml must carry the sha256: prefix."""
123
124 def test_derive_hd_public_info_returns_prefixed_fingerprint(self) -> None:
125 from muse.core.keypair import derive_hd_public_info
126 seed = b"\x00" * 64
127 _, fingerprint = derive_hd_public_info(seed)
128 assert fingerprint.startswith("sha256:"), (
129 f"derive_hd_public_info returned bare fingerprint {fingerprint!r} — "
130 "must be sha256:-prefixed"
131 )
132
133 def test_fingerprint_field_in_identity_entry_is_prefixed(self, tmp_path: pathlib.Path) -> None:
134 """Round-trip: save an identity, load it back, fingerprint has prefix."""
135 from muse.core.identity import save_identity, load_identity
136 from muse.core.types import public_key_fingerprint
137 import secrets
138
139 fingerprint = public_key_fingerprint(b"fake-public-key-bytes")
140 entry = {
141 "type": "human",
142 "handle": "gabriel",
143 "key_path": str(tmp_path / "key.pem"),
144 "algorithm": "ed25519",
145 "fingerprint": fingerprint,
146 }
147 # Monkeypatch the identity file location
148 import muse.core.identity as id_mod
149 orig = id_mod._IDENTITY_FILE
150 id_mod._IDENTITY_FILE = tmp_path / "identity.toml"
151 try:
152 save_identity("localhost:1337", entry) # type: ignore[arg-type]
153 loaded = load_identity("localhost:1337")
154 assert loaded is not None
155 assert loaded.get("fingerprint", "").startswith("sha256:"), (
156 f"Loaded fingerprint {loaded.get('fingerprint')!r} lacks sha256: prefix"
157 )
158 finally:
159 id_mod._IDENTITY_FILE = orig
File History 5 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:c10a2ce474b3bb7ff2a3d628e8a3f2e028fd78ca652513496a03a498ae2267b3 chore: sweep all stale DirectoryRenameOp / directory_rename… Sonnet 4.6 minor 23 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 28 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago