gabriel / muse public
test_core_msign.py python
626 lines 25.5 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
1 """Comprehensive tests for muse.core.msign — MSign signing primitives.
2
3 Coverage
4 --------
5
6 Unit
7 - canonical_message: format, body hash, empty body, query string, host, alg
8 - build_msign_header: structure, format, alg field, host extraction
9 - parse_msign_header: valid headers, all error paths, alg field
10 - verify_msign_header: valid round-trip, tampered body, expired timestamp,
11 wrong key, bad public key, bad signature, replay window edge cases
12 - build_payment_claim: structure, canonical message format, chain linkage
13
14 Data integrity
15 - Ed25519 is deterministic: same key+message+ts → same signature (RFC 8032)
16 - canonical_message is byte-exact (regression against known test vectors)
17 - Empty body → SHA-256 of b"" (not of "null", "{}", or anything else)
18 - Body hash covers raw bytes, not re-serialized JSON
19 - Path includes query string when present
20 - Host is included and normalised (standard ports stripped)
21 - Algorithm is the first field in canonical message
22
23 Performance
24 - 10 000 sequential build_msign_header calls in < 2 s
25 - build_msign_header has no I/O on the hot path
26
27 Security
28 - verify rejects tampered body
29 - verify rejects expired timestamp (> max_age seconds)
30 - verify rejects future timestamp (> max_age seconds ahead)
31 - verify rejects signature from a different key
32 - verify rejects truncated signature
33 - verify rejects garbage header
34 - different host → different signature (host is in canonical)
35 """
36
37 from __future__ import annotations
38
39 import base64
40 import hashlib
41 import time
42 from typing import NamedTuple
43
44 from muse.core._types import blob_id
45
46 import pytest
47 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
48
49
50 # ---------------------------------------------------------------------------
51 # Fixtures and helpers
52 # ---------------------------------------------------------------------------
53
54 class _Identity(NamedTuple):
55 handle: str
56 private_key: Ed25519PrivateKey
57 public_key_b64: str
58
59
60 def _make_identity(handle: str = "testuser") -> _Identity:
61 pk = Ed25519PrivateKey.generate()
62 pub_bytes = pk.public_key().public_bytes_raw()
63 pub_b64 = base64.urlsafe_b64encode(pub_bytes).rstrip(b"=").decode("ascii")
64 return _Identity(handle=handle, private_key=pk, public_key_b64=pub_b64)
65
66
67 # Fixed seed for determinism tests — do NOT change.
68 _KNOWN_SEED = bytes(range(32))
69
70
71 def _known_identity() -> _Identity:
72 pk = Ed25519PrivateKey.from_private_bytes(_KNOWN_SEED)
73 pub_bytes = pk.public_key().public_bytes_raw()
74 pub_b64 = base64.urlsafe_b64encode(pub_bytes).rstrip(b"=").decode("ascii")
75 return _Identity(handle="gabriel", private_key=pk, public_key_b64=pub_b64)
76
77
78 # ---------------------------------------------------------------------------
79 # Unit: canonical_message
80 # ---------------------------------------------------------------------------
81
82 class TestCanonicalMessage:
83 def test_format(self) -> None:
84 from muse.core.msign import canonical_message
85 msg = canonical_message(
86 "POST", "/gabriel/muse/push", 1744000000, b"hello",
87 host="staging.musehub.ai",
88 )
89 body_hash = blob_id(b"hello")
90 expected = f"ed25519\nPOST\nstaging.musehub.ai\n/gabriel/muse/push\n1744000000\n{body_hash}"
91 assert msg == expected.encode()
92
93 def test_empty_body_uses_empty_sha256(self) -> None:
94 from muse.core.msign import canonical_message, EMPTY_BODY_HASH
95 msg = canonical_message("GET", "/x", 1, b"", host="hub.example.com")
96 assert EMPTY_BODY_HASH in msg.decode()
97 assert "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" in msg.decode()
98
99 def test_query_string_included(self) -> None:
100 from muse.core.msign import canonical_message
101 msg = canonical_message(
102 "GET", "/search?q=foo&page=2", 1, b"", host="hub.example.com"
103 )
104 assert b"/search?q=foo&page=2" in msg
105
106 def test_method_uppercase(self) -> None:
107 from muse.core.msign import canonical_message
108 msg = canonical_message("DELETE", "/x", 1, b"", host="hub.example.com")
109 parts = msg.decode().split("\n")
110 assert parts[1] == "DELETE"
111
112 def test_body_hash_covers_raw_bytes_not_repr(self) -> None:
113 from muse.core.msign import canonical_message
114 body = b'{"key": "value"}'
115 expected = blob_id(body)
116 msg = canonical_message("POST", "/x", 1, body, host="h")
117 assert expected in msg.decode()
118 assert blob_id(repr(body).encode()) not in msg.decode()
119
120 def test_six_newline_separated_fields(self) -> None:
121 from muse.core.msign import canonical_message
122 msg = canonical_message("POST", "/path", 12345, b"body", host="localhost:1337")
123 parts = msg.decode().split("\n")
124 assert len(parts) == 6
125 assert parts[0] == "ed25519"
126 assert parts[1] == "POST"
127 assert parts[2] == "localhost:1337"
128 assert parts[3] == "/path"
129 assert parts[4] == "12345"
130 assert parts[5] == blob_id(b"body")
131
132 def test_algorithm_is_first_field(self) -> None:
133 from muse.core.msign import canonical_message
134 msg = canonical_message("GET", "/x", 1, b"", host="h")
135 assert msg.startswith(b"ed25519\n")
136
137 def test_host_in_canonical(self) -> None:
138 from muse.core.msign import canonical_message
139 msg = canonical_message("GET", "/x", 1, b"", host="staging.musehub.ai")
140 assert b"staging.musehub.ai" in msg
141
142 def test_different_hosts_different_output(self) -> None:
143 from muse.core.msign import canonical_message
144 m1 = canonical_message("GET", "/x", 1, b"", host="host-a.com")
145 m2 = canonical_message("GET", "/x", 1, b"", host="host-b.com")
146 assert m1 != m2
147
148 def test_custom_algorithm_field(self) -> None:
149 from muse.core.msign import canonical_message
150 msg = canonical_message("GET", "/x", 1, b"", host="h", algorithm="ed25519-v2")
151 assert msg.startswith(b"ed25519-v2\n")
152
153
154 # ---------------------------------------------------------------------------
155 # Unit: build_msign_header
156 # ---------------------------------------------------------------------------
157
158 class TestBuildMsignHeader:
159 def test_format(self) -> None:
160 from muse.core.msign import build_msign_header
161 identity = _make_identity("gabriel")
162 url = "https://staging.musehub.ai/gabriel/muse/push"
163 header = build_msign_header(identity, "POST", url, b"body", ts=1744000000)
164 assert header.startswith('MSign handle="gabriel" alg="ed25519" ts=1744000000 sig="')
165 assert header.endswith('"')
166
167 def test_contains_all_components(self) -> None:
168 from muse.core.msign import build_msign_header
169 identity = _make_identity("alice")
170 header = build_msign_header(identity, "GET", "https://hub.example.com/x", ts=9999)
171 assert 'handle="alice"' in header
172 assert 'alg="ed25519"' in header
173 assert "ts=9999" in header
174 assert 'sig="' in header
175
176 def test_sig_is_base64url_no_padding(self) -> None:
177 from muse.core.msign import build_msign_header
178 identity = _make_identity()
179 header = build_msign_header(identity, "POST", "https://hub.example.com/x", b"", ts=1)
180 sig = header.split('sig="')[1].rstrip('"')
181 assert "=" not in sig
182 assert "+" not in sig
183 assert "/" not in sig
184 pad = (4 - len(sig) % 4) % 4
185 decoded = base64.urlsafe_b64decode(sig + "=" * pad)
186 assert len(decoded) == 64
187
188 def test_none_body_treated_as_empty(self) -> None:
189 from muse.core.msign import build_msign_header
190 identity = _make_identity()
191 h1 = build_msign_header(identity, "GET", "https://hub.example.com/x", None, ts=1)
192 h2 = build_msign_header(identity, "GET", "https://hub.example.com/x", b"", ts=1)
193 assert h1 == h2
194
195 def test_url_query_string_included_in_signing(self) -> None:
196 from muse.core.msign import build_msign_header
197 identity = _make_identity()
198 h1 = build_msign_header(identity, "GET", "https://hub.example.com/x?a=1", ts=1)
199 h2 = build_msign_header(identity, "GET", "https://hub.example.com/x?a=2", ts=1)
200 assert h1 != h2
201
202 def test_standard_https_port_stripped(self) -> None:
203 """Port 443 on https must be stripped from host in canonical message."""
204 from muse.core.msign import build_msign_header
205 identity = _make_identity()
206 # With explicit :443 and without should produce identical signatures.
207 h1 = build_msign_header(identity, "GET", "https://hub.example.com:443/x", ts=1)
208 h2 = build_msign_header(identity, "GET", "https://hub.example.com/x", ts=1)
209 assert h1 == h2
210
211 def test_standard_http_port_stripped(self) -> None:
212 from muse.core.msign import build_msign_header
213 identity = _make_identity()
214 h1 = build_msign_header(identity, "GET", "http://hub.example.com:80/x", ts=1)
215 h2 = build_msign_header(identity, "GET", "http://hub.example.com/x", ts=1)
216 assert h1 == h2
217
218 def test_nonstandard_port_kept(self) -> None:
219 """localhost:1337 is non-standard — the port must stay in the canonical host."""
220 from muse.core.msign import build_msign_header
221 identity = _make_identity()
222 h1 = build_msign_header(identity, "GET", "https://localhost:1337/x", ts=1)
223 h2 = build_msign_header(identity, "GET", "http://localhost/x", ts=1)
224 assert h1 != h2
225
226 def test_different_hosts_produce_different_sigs(self) -> None:
227 from muse.core.msign import build_msign_header
228 identity = _make_identity()
229 h1 = build_msign_header(identity, "GET", "https://host-a.example.com/x", ts=1)
230 h2 = build_msign_header(identity, "GET", "https://host-b.example.com/x", ts=1)
231 assert h1 != h2
232
233
234 # ---------------------------------------------------------------------------
235 # Data integrity: Ed25519 determinism (RFC 8032)
236 # ---------------------------------------------------------------------------
237
238 class TestDeterminism:
239 def test_same_inputs_same_signature(self) -> None:
240 from muse.core.msign import build_msign_header
241 identity = _known_identity()
242 url = "https://staging.musehub.ai/path?q=1"
243 body = b"fixed body"
244 results = [
245 build_msign_header(identity, "POST", url, body, ts=1000)
246 for _ in range(10)
247 ]
248 assert len(set(results)) == 1, "Ed25519 must be deterministic"
249
250 def test_different_ts_different_signature(self) -> None:
251 from muse.core.msign import build_msign_header
252 identity = _known_identity()
253 h1 = build_msign_header(identity, "POST", "https://hub.example.com/x", b"", ts=1)
254 h2 = build_msign_header(identity, "POST", "https://hub.example.com/x", b"", ts=2)
255 assert h1 != h2
256
257 def test_different_body_different_signature(self) -> None:
258 from muse.core.msign import build_msign_header
259 identity = _known_identity()
260 h1 = build_msign_header(identity, "POST", "https://hub.example.com/x", b"aaa", ts=1)
261 h2 = build_msign_header(identity, "POST", "https://hub.example.com/x", b"bbb", ts=1)
262 assert h1 != h2
263
264 def test_known_vector(self) -> None:
265 """Regression: known seed → known signature (catches canonical_message drift)."""
266 import urllib.parse
267 from muse.core.msign import build_msign_header, canonical_message, _normalise_host
268 identity = _known_identity()
269 ts = 1744000000
270 url = "https://staging.musehub.ai/gabriel/muse/push"
271 body = b""
272 parsed = urllib.parse.urlparse(url)
273 path = parsed.path
274 host = _normalise_host(parsed)
275 msg = canonical_message("POST", path, ts, body, host=host)
276 sig_bytes = identity.private_key.sign(msg)
277 expected_sig = base64.urlsafe_b64encode(sig_bytes).rstrip(b"=").decode("ascii")
278 header = build_msign_header(identity, "POST", url, body, ts=ts)
279 actual_sig = header.split('sig="')[1].rstrip('"')
280 assert actual_sig == expected_sig
281
282
283 # ---------------------------------------------------------------------------
284 # Unit: parse_msign_header
285 # ---------------------------------------------------------------------------
286
287 class TestParseMsignHeader:
288 def test_valid_header(self) -> None:
289 from muse.core.msign import parse_msign_header
290 h = 'MSign handle="gabriel" alg="ed25519" ts=1744000000 sig="aBcDeFg"'
291 parsed = parse_msign_header(h)
292 assert parsed["handle"] == "gabriel"
293 assert parsed["alg"] == "ed25519"
294 assert parsed["ts"] == 1744000000
295 assert parsed["sig"] == "aBcDeFg"
296
297 def test_full_authorization_prefix(self) -> None:
298 from muse.core.msign import parse_msign_header
299 h = 'Authorization: MSign handle="alice" alg="ed25519" ts=1 sig="xyz"'
300 parsed = parse_msign_header(h)
301 assert parsed["handle"] == "alice"
302 assert parsed["alg"] == "ed25519"
303
304 def test_invalid_raises_value_error(self) -> None:
305 from muse.core.msign import parse_msign_header
306 with pytest.raises(ValueError, match="Not a valid MSign header"):
307 parse_msign_header("Bearer token123")
308
309 def test_old_four_field_format_raises(self) -> None:
310 """Old format without alg must be rejected."""
311 from muse.core.msign import parse_msign_header
312 with pytest.raises(ValueError):
313 parse_msign_header('MSign handle="x" ts=42 sig="s"')
314
315 def test_empty_raises_value_error(self) -> None:
316 from muse.core.msign import parse_msign_header
317 with pytest.raises(ValueError):
318 parse_msign_header("")
319
320 def test_ts_is_int(self) -> None:
321 from muse.core.msign import parse_msign_header
322 parsed = parse_msign_header('MSign handle="x" alg="ed25519" ts=42 sig="s"')
323 assert isinstance(parsed["ts"], int)
324
325 def test_alg_field_present(self) -> None:
326 from muse.core.msign import parse_msign_header
327 parsed = parse_msign_header('MSign handle="x" alg="ed25519" ts=1 sig="abc"')
328 assert "alg" in parsed
329 assert parsed["alg"] == "ed25519"
330
331 def test_roundtrip_with_build(self) -> None:
332 from muse.core.msign import build_msign_header, parse_msign_header
333 identity = _make_identity("gabriel")
334 header = build_msign_header(
335 identity, "POST", "https://staging.musehub.ai/gabriel/muse/push",
336 b"body", ts=1744000000,
337 )
338 parsed = parse_msign_header(header)
339 assert parsed["handle"] == "gabriel"
340 assert parsed["alg"] == "ed25519"
341 assert parsed["ts"] == 1744000000
342
343
344 # ---------------------------------------------------------------------------
345 # Unit: verify_msign_header
346 # ---------------------------------------------------------------------------
347
348 class TestVerifyMsignHeader:
349 def _sign_and_header(
350 self,
351 identity: _Identity,
352 method: str = "POST",
353 url: str = "https://hub.example.com/path",
354 body: bytes = b"",
355 ts: int = 1744000000,
356 ) -> str:
357 from muse.core.msign import build_msign_header
358 return build_msign_header(identity, method, url, body, ts=ts)
359
360 def test_valid_round_trip(self) -> None:
361 from muse.core.msign import verify_msign_header
362 identity = _make_identity("gabriel")
363 url = "https://staging.musehub.ai/gabriel/muse/push"
364 body = b"some body content"
365 ts = int(time.time())
366 header = self._sign_and_header(identity, "POST", url, body, ts=ts)
367 ok, reason = verify_msign_header(
368 header, "POST", url, body, identity.public_key_b64,
369 max_age=300, now=ts,
370 )
371 assert ok, f"Expected valid, got: {reason}"
372 assert reason == "ok"
373
374 def test_tampered_body_rejected(self) -> None:
375 from muse.core.msign import verify_msign_header
376 identity = _make_identity()
377 url = "https://hub.example.com/x"
378 ts = int(time.time())
379 header = self._sign_and_header(identity, "POST", url, b"original", ts=ts)
380 ok, reason = verify_msign_header(
381 header, "POST", url, b"TAMPERED", identity.public_key_b64,
382 max_age=300, now=ts,
383 )
384 assert not ok
385 assert "signature" in reason.lower() or "failed" in reason.lower()
386
387 def test_expired_timestamp_rejected(self) -> None:
388 from muse.core.msign import verify_msign_header
389 identity = _make_identity()
390 url = "https://hub.example.com/x"
391 ts = 1000
392 header = self._sign_and_header(identity, "POST", url, b"", ts=ts)
393 ok, reason = verify_msign_header(
394 header, "POST", url, b"", identity.public_key_b64,
395 max_age=30, now=ts + 60,
396 )
397 assert not ok
398 assert "replay window" in reason
399
400 def test_future_timestamp_rejected(self) -> None:
401 from muse.core.msign import verify_msign_header
402 identity = _make_identity()
403 url = "https://hub.example.com/x"
404 ts = 9_000_000_000
405 header = self._sign_and_header(identity, "POST", url, b"", ts=ts)
406 ok, reason = verify_msign_header(
407 header, "POST", url, b"", identity.public_key_b64,
408 max_age=30, now=int(time.time()),
409 )
410 assert not ok
411 assert "replay window" in reason
412
413 def test_timestamp_at_edge_of_window_accepted(self) -> None:
414 from muse.core.msign import verify_msign_header
415 identity = _make_identity()
416 url = "https://hub.example.com/x"
417 ts = 1000
418 header = self._sign_and_header(identity, "POST", url, b"", ts=ts)
419 ok, _ = verify_msign_header(
420 header, "POST", url, b"", identity.public_key_b64,
421 max_age=30, now=ts + 30,
422 )
423 assert ok
424
425 def test_wrong_key_rejected(self) -> None:
426 from muse.core.msign import verify_msign_header
427 signer = _make_identity("alice")
428 verifier = _make_identity("bob")
429 url = "https://hub.example.com/x"
430 ts = int(time.time())
431 header = self._sign_and_header(signer, "POST", url, b"", ts=ts)
432 ok, reason = verify_msign_header(
433 header, "POST", url, b"", verifier.public_key_b64,
434 max_age=300, now=ts,
435 )
436 assert not ok
437 assert "signature" in reason.lower() or "failed" in reason.lower()
438
439 def test_bad_public_key_rejected(self) -> None:
440 from muse.core.msign import verify_msign_header
441 identity = _make_identity()
442 url = "https://hub.example.com/x"
443 ts = int(time.time())
444 header = self._sign_and_header(identity, "POST", url, b"", ts=ts)
445 ok, reason = verify_msign_header(
446 header, "POST", url, b"", "not-valid-base64!!!",
447 max_age=300, now=ts,
448 )
449 assert not ok
450 assert "public key" in reason.lower() or "invalid" in reason.lower()
451
452 def test_garbage_header_rejected(self) -> None:
453 from muse.core.msign import verify_msign_header
454 identity = _make_identity()
455 ok, reason = verify_msign_header(
456 "Bearer totally-not-msign", "POST", "https://hub.example.com/x", b"",
457 identity.public_key_b64, max_age=30,
458 )
459 assert not ok
460 assert "Not a valid MSign header" in reason
461
462 def test_none_body_same_as_empty(self) -> None:
463 from muse.core.msign import build_msign_header, verify_msign_header
464 identity = _make_identity()
465 url = "https://hub.example.com/x"
466 ts = int(time.time())
467 header = build_msign_header(identity, "GET", url, None, ts=ts)
468 ok, _ = verify_msign_header(
469 header, "GET", url, None, identity.public_key_b64,
470 max_age=300, now=ts,
471 )
472 assert ok
473
474 def test_different_method_rejected(self) -> None:
475 from muse.core.msign import verify_msign_header
476 identity = _make_identity()
477 url = "https://hub.example.com/x"
478 ts = int(time.time())
479 header = self._sign_and_header(identity, "POST", url, b"", ts=ts)
480 ok, _ = verify_msign_header(
481 header, "GET", url, b"", identity.public_key_b64,
482 max_age=300, now=ts,
483 )
484 assert not ok
485
486 def test_different_url_rejected(self) -> None:
487 from muse.core.msign import verify_msign_header
488 identity = _make_identity()
489 ts = int(time.time())
490 header = self._sign_and_header(identity, "POST", "https://hub.example.com/push", b"", ts=ts)
491 ok, _ = verify_msign_header(
492 header, "POST", "https://hub.example.com/pull", b"", identity.public_key_b64,
493 max_age=300, now=ts,
494 )
495 assert not ok
496
497 def test_different_host_rejected(self) -> None:
498 """Signature for host-a must not verify against host-b."""
499 from muse.core.msign import verify_msign_header
500 identity = _make_identity()
501 ts = int(time.time())
502 header = self._sign_and_header(
503 identity, "POST", "https://host-a.example.com/x", b"", ts=ts
504 )
505 ok, _ = verify_msign_header(
506 header, "POST", "https://host-b.example.com/x", b"",
507 identity.public_key_b64, max_age=300, now=ts,
508 )
509 assert not ok
510
511 def test_localhost_nonstandard_port_in_canonical(self) -> None:
512 """localhost:1337 must be in canonical — round-trip must pass."""
513 from muse.core.msign import verify_msign_header
514 identity = _make_identity("gabriel")
515 url = "https://localhost:1337/gabriel/muse/push"
516 ts = int(time.time())
517 header = self._sign_and_header(identity, "POST", url, b"payload", ts=ts)
518 ok, reason = verify_msign_header(
519 header, "POST", url, b"payload", identity.public_key_b64,
520 max_age=300, now=ts,
521 )
522 assert ok, f"Expected valid, got: {reason}"
523
524
525 # ---------------------------------------------------------------------------
526 # Unit: build_payment_claim
527 # ---------------------------------------------------------------------------
528
529 class TestBuildPaymentClaim:
530 def test_structure(self) -> None:
531 from muse.core.msign import build_payment_claim
532 identity = _make_identity("gabriel")
533 claim = build_payment_claim(
534 identity,
535 from_handle="gabriel",
536 to_handle="stori-node-1",
537 amount_nano=1_000_000,
538 currency="nanoMUSE",
539 nonce_hex="a" * 64,
540 memo="stem:sha256:abc123",
541 ts=1744000000,
542 )
543 assert claim["from_handle"] == "gabriel"
544 assert claim["to_handle"] == "stori-node-1"
545 assert claim["amount_nano"] == 1_000_000
546 assert claim["currency"] == "nanoMUSE"
547 assert claim["nonce_hex"] == "a" * 64
548 assert claim["memo"] == "stem:sha256:abc123"
549 assert claim["ts"] == 1744000000
550 assert "signature_b64" in claim
551 assert "canonical_message" in claim
552
553 def test_canonical_message_format(self) -> None:
554 from muse.core.msign import build_payment_claim
555 identity = _make_identity()
556 claim = build_payment_claim(
557 identity, "alice", "bob", 500, "nanoMUSE", "ff" * 32, "memo", ts=1
558 )
559 msg = claim["canonical_message"]
560 assert msg.startswith("MPAY\n")
561 parts = msg.split("\n")
562 assert parts[0] == "MPAY"
563 assert parts[1] == "alice"
564 assert parts[2] == "bob"
565 assert parts[3] == "500"
566 assert parts[4] == "nanoMUSE"
567 assert parts[6] == "memo"
568 assert parts[7] == "1"
569
570 def test_domain_separation_from_http_signing(self) -> None:
571 """MPAY canonical message must differ from MSign canonical message."""
572 from muse.core.msign import build_msign_header, build_payment_claim
573 identity = _make_identity()
574 ts = 1744000000
575 http_header = build_msign_header(
576 identity, "POST", "https://hub.example.com/alice", b"", ts=ts
577 )
578 claim = build_payment_claim(
579 identity, "alice", "bob", 100, "nanoMUSE", "00" * 32, "", ts=ts
580 )
581 http_sig = http_header.split('sig="')[1].rstrip('"')
582 pay_sig = claim["signature_b64"]
583 assert http_sig != pay_sig
584
585 def test_deterministic(self) -> None:
586 from muse.core.msign import build_payment_claim
587 identity = _known_identity()
588 claims = [
589 build_payment_claim(identity, "a", "b", 1, "nanoMUSE", "0" * 64, "", ts=42)
590 for _ in range(5)
591 ]
592 sigs = [c["signature_b64"] for c in claims]
593 assert len(set(sigs)) == 1
594
595 def test_chain_linkage(self) -> None:
596 from muse.core.msign import build_payment_claim
597 identity = _make_identity()
598 claim1 = build_payment_claim(
599 identity, "gabriel", "node1", 100, "nanoMUSE", "0" * 64, "first", ts=1
600 )
601 nonce2 = hashlib.sha256(claim1["signature_b64"].encode()).hexdigest()
602 claim2 = build_payment_claim(
603 identity, "gabriel", "node1", 100, "nanoMUSE", nonce2, "second", ts=2
604 )
605 assert claim2["nonce_hex"] == nonce2
606 assert claim2["signature_b64"] != claim1["signature_b64"]
607
608
609 # ---------------------------------------------------------------------------
610 # Performance
611 # ---------------------------------------------------------------------------
612
613 class TestPerformance:
614 def test_10000_sequential_header_calls_under_2s(self) -> None:
615 from muse.core.msign import build_msign_header
616 identity = _known_identity()
617 url = "https://staging.musehub.ai/gabriel/muse/push"
618 body = b"benchmark body"
619 start = time.perf_counter()
620 for i in range(10_000):
621 build_msign_header(identity, "POST", url, body, ts=i)
622 elapsed = time.perf_counter() - start
623 assert elapsed < 2.0, (
624 f"10 000 build_msign_header calls took {elapsed:.2f}s — expected < 2s. "
625 "Check for unexpected I/O or import overhead on the hot path."
626 )
File History 3 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 138 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 140 days ago