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