gabriel / muse public

test_resolve_signing_identity_keychain_path.py file-level

at sha256:d · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:b adding issues docs to bust staging mpack prebuild cache. · gabriel · Jun 20, 2026
1 """Full chain tests for resolve_signing_identity.
2
3 Verifies the complete keychain β†’ derive β†’ sign β†’ verify path with no disk reads
4 from ~/.muse/keys/.
5
6 Chain invariants
7 ----------------
8 KC-1 keychain β†’ mnemonic_to_seed β†’ derive_path β†’ Ed25519PrivateKey β€” full chain
9 produces a usable signing key.
10 KC-2 Derived key signs a canonical message verifiable with the stored public key.
11 KC-3 No mnemonic in keychain β†’ returns None gracefully.
12 KC-4 No identity entry for hub β†’ returns None gracefully.
13 KC-5 Identity entry missing hd_path β†’ returns None gracefully.
14 KC-6 Same mnemonic + same hd_path β†’ always the same key (deterministic).
15 KC-7 Different hd_paths β†’ different keys.
16 """
17
18 from __future__ import annotations
19
20 import pathlib
21
22 import pytest
23 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
24
25 from muse.core.identity import save_identity, resolve_signing_identity
26 from muse.core.keypair import derive_hd_public_info
27 from muse.core.bip39 import mnemonic_to_seed
28
29 _MNEMONIC = (
30 "abandon abandon abandon abandon abandon abandon abandon abandon "
31 "abandon abandon abandon about"
32 )
33 _HUB = "https://localhost:1337"
34 _HD_PATH = "m/1075233755'/0'/0'/0'/0'/0'"
35 _AGENT_HD_PATH = "m/1075233755'/1'/0'/0'/0'/0'"
36
37
38 # ---------------------------------------------------------------------------
39 # Fixtures
40 # ---------------------------------------------------------------------------
41
42
43 @pytest.fixture()
44 def isolated_env(
45 monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
46 ) -> pathlib.Path:
47 import muse.core.keypair as kp_module
48 import muse.core.identity as id_module
49
50 fake_home = tmp_path / "home"
51 fake_home.mkdir(parents=True, exist_ok=True)
52 monkeypatch.setattr(pathlib.Path, "home", staticmethod(lambda: fake_home))
53 monkeypatch.setattr(kp_module, "_KEYS_DIR", fake_home / ".muse" / "keys")
54 monkeypatch.setattr(id_module, "_IDENTITY_DIR", fake_home / ".muse")
55 monkeypatch.setattr(id_module, "_IDENTITY_FILE", fake_home / ".muse" / "identity.toml")
56 return fake_home
57
58
59 @pytest.fixture()
60 def keychain_mnemonic(monkeypatch: pytest.MonkeyPatch) -> None:
61 import muse.core.keychain as kc
62 monkeypatch.setattr(kc, "is_available", lambda: True)
63 monkeypatch.setattr(kc, "load", lambda: _MNEMONIC)
64
65
66 def _save_human_entry(fingerprint: str) -> None:
67 save_identity(_HUB, {
68 "type": "human",
69 "handle": "gabriel",
70 "algorithm": "ed25519",
71 "fingerprint": fingerprint,
72 "hd_path": _HD_PATH,
73 })
74
75
76 def _fingerprint() -> str:
77 seed = mnemonic_to_seed(_MNEMONIC)
78 _, fp = derive_hd_public_info(seed)
79 return fp
80
81
82 # ---------------------------------------------------------------------------
83 # KC-1 β€” full chain returns a private key
84 # ---------------------------------------------------------------------------
85
86
87 class TestFullChain:
88 def test_KC_1_returns_signing_identity(
89 self,
90 isolated_env: pathlib.Path,
91 keychain_mnemonic: None,
92 ) -> None:
93 """KC-1: full chain returns (handle, Ed25519PrivateKey)."""
94 _save_human_entry(_fingerprint())
95 result = resolve_signing_identity(_HUB)
96 assert result is not None, "resolve_signing_identity returned None"
97 handle, private_key = result
98 assert handle == "gabriel"
99 assert isinstance(private_key, Ed25519PrivateKey)
100
101 def test_KC_2_derived_key_signs_verifiable_message(
102 self,
103 isolated_env: pathlib.Path,
104 keychain_mnemonic: None,
105 ) -> None:
106 """KC-2: the derived key produces a signature verifiable with stored pubkey."""
107 from muse.core.slip010 import derive_path, to_ed25519_private_key
108 from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
109
110 seed = mnemonic_to_seed(_MNEMONIC)
111 dk = derive_path(seed, _HD_PATH)
112 expected_key = to_ed25519_private_key(dk)
113 dk.zero()
114 expected_pub = expected_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
115
116 _save_human_entry(_fingerprint())
117 result = resolve_signing_identity(_HUB)
118 assert result is not None
119 _, private_key = result
120
121 message = b"test canonical message"
122 sig = private_key.sign(message)
123 derived_pub = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
124
125 assert derived_pub == expected_pub, "Derived public key does not match expected"
126 # Verify signature with expected public key
127 expected_key.public_key().verify(sig, message) # raises if invalid
128
129
130 # ---------------------------------------------------------------------------
131 # KC-3 to KC-5 β€” graceful None returns
132 # ---------------------------------------------------------------------------
133
134
135 class TestGracefulNone:
136 def test_KC_3_no_mnemonic_returns_none(
137 self,
138 isolated_env: pathlib.Path,
139 monkeypatch: pytest.MonkeyPatch,
140 ) -> None:
141 """KC-3: no mnemonic in keychain β†’ None."""
142 import muse.core.keychain as kc
143 monkeypatch.setattr(kc, "is_available", lambda: True)
144 monkeypatch.setattr(kc, "load", lambda: None)
145 _save_human_entry(_fingerprint())
146 assert resolve_signing_identity(_HUB) is None
147
148 def test_KC_4_no_identity_entry_returns_none(
149 self,
150 isolated_env: pathlib.Path,
151 keychain_mnemonic: None,
152 ) -> None:
153 """KC-4: no identity.toml entry for hub β†’ None."""
154 assert resolve_signing_identity(_HUB) is None
155
156 def test_KC_5_no_hd_path_returns_none(
157 self,
158 isolated_env: pathlib.Path,
159 keychain_mnemonic: None,
160 ) -> None:
161 """KC-5: identity entry missing hd_path β†’ None."""
162 save_identity(_HUB, {
163 "type": "human",
164 "handle": "gabriel",
165 "algorithm": "ed25519",
166 "fingerprint": _fingerprint(),
167 })
168 assert resolve_signing_identity(_HUB) is None
169
170
171 # ---------------------------------------------------------------------------
172 # KC-6 β€” deterministic derivation
173 # ---------------------------------------------------------------------------
174
175
176 class TestDeterministic:
177 def test_KC_6_same_mnemonic_same_key(
178 self,
179 isolated_env: pathlib.Path,
180 keychain_mnemonic: None,
181 ) -> None:
182 """KC-6: same mnemonic + hd_path always produces the same public key bytes."""
183 from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
184
185 _save_human_entry(_fingerprint())
186 r1 = resolve_signing_identity(_HUB)
187 r2 = resolve_signing_identity(_HUB)
188 assert r1 is not None and r2 is not None
189 pub1 = r1[1].public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
190 pub2 = r2[1].public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
191 assert pub1 == pub2, "Derivation is not deterministic"
192
193 def test_KC_7_different_hd_paths_different_keys(
194 self,
195 isolated_env: pathlib.Path,
196 keychain_mnemonic: None,
197 ) -> None:
198 """KC-7: different hd_paths β†’ different public keys."""
199 from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
200 from muse.core.slip010 import derive_path, to_ed25519_private_key
201
202 seed = mnemonic_to_seed(_MNEMONIC)
203
204 dk1 = derive_path(seed, _HD_PATH)
205 key1 = to_ed25519_private_key(dk1)
206 dk1.zero()
207
208 dk2 = derive_path(seed, _AGENT_HD_PATH)
209 key2 = to_ed25519_private_key(dk2)
210 dk2.zero()
211
212 pub1 = key1.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
213 pub2 = key2.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
214 assert pub1 != pub2, "Different HD paths must produce different keys"