gabriel / muse public
test_core_provenance.py python
166 lines 6.1 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 28 days ago
1 """Provenance payload v2 tests — timestamp binding and version prefix.
2
3 These tests verify the v2 provenance signing payload format:
4 - Version prefix "muse-provenance-v2\n"
5 - committed_at timestamp included as the last field
6 - Signature detects timestamp mutation
7 """
8
9 from __future__ import annotations
10
11 import base64
12
13 import pytest
14 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
15 from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
16
17 from muse.core.provenance import (
18 PROVENANCE_PAYLOAD_VERSION,
19 _PROV_VERSION_PREFIX,
20 provenance_payload,
21 sign_commit_ed25519,
22 sign_commit_record,
23 verify_commit_ed25519,
24 )
25
26
27 def _gen_key() -> Ed25519PrivateKey:
28 return Ed25519PrivateKey.generate()
29
30
31 def _pub_bytes(key: Ed25519PrivateKey) -> bytes:
32 return key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
33
34
35 # ---------------------------------------------------------------------------
36 # Version constant
37 # ---------------------------------------------------------------------------
38
39
40 def test_provenance_payload_version_is_2() -> None:
41 """PROVENANCE_PAYLOAD_VERSION constant is 2."""
42 assert PROVENANCE_PAYLOAD_VERSION == 2
43
44
45 # ---------------------------------------------------------------------------
46 # Control 3: committed_at in payload
47 # ---------------------------------------------------------------------------
48
49
50 class TestProvenancePayloadV2:
51 def test_payload_version_prefix(self) -> None:
52 """Payload is prefixed with 'muse-provenance-v2\\n'."""
53 import hashlib
54 commit_id = "a" * 64
55 # Reconstruct the raw payload and check the prefix.
56 # provenance_payload returns the SHA-256 hex of the raw bytes, so we
57 # can verify by computing manually.
58 fields = [commit_id, "", "", "", "", "", ""]
59 raw = (_PROV_VERSION_PREFIX + "\x00".join(fields)).encode()
60 expected = hashlib.sha256(raw).hexdigest()
61 actual = provenance_payload(commit_id)
62 assert actual == expected
63
64 def test_payload_includes_committed_at(self) -> None:
65 """committed_at appears as the last field in the canonical payload."""
66 import hashlib
67 commit_id = "b" * 64
68 ts = "2026-04-08T12:00:00"
69 fields = [commit_id, "", "", "", "", "", ts]
70 raw = (_PROV_VERSION_PREFIX + "\x00".join(fields)).encode()
71 expected = hashlib.sha256(raw).hexdigest()
72 actual = provenance_payload(commit_id, committed_at=ts)
73 assert actual == expected
74
75 def test_different_committed_at_different_payload(self) -> None:
76 """Two different committed_at values → different payload hashes."""
77 commit_id = "c" * 64
78 p1 = provenance_payload(commit_id, committed_at="2026-01-01T00:00:00")
79 p2 = provenance_payload(commit_id, committed_at="2026-01-02T00:00:00")
80 assert p1 != p2
81
82 def test_empty_committed_at_still_works(self) -> None:
83 """Empty committed_at is valid (backward compat for unsigned commits)."""
84 commit_id = "d" * 64
85 # Should not raise.
86 payload = provenance_payload(commit_id, committed_at="")
87 assert isinstance(payload, str)
88 assert len(payload) == 64
89
90 def test_payload_is_64_hex_chars(self) -> None:
91 """Return value is always a 64-character hex string."""
92 payload = provenance_payload("e" * 64, committed_at="2026-04-08T00:00:00")
93 assert len(payload) == 64
94 assert all(c in "0123456789abcdef" for c in payload)
95
96
97 class TestTimestampSignatureBinding:
98 def test_signature_detects_timestamp_mutation(self) -> None:
99 """Sign with timestamp T1, verify with T2 → fails."""
100 key = _gen_key()
101 commit_id = "f" * 64
102 ts1 = "2026-04-08T12:00:00"
103 ts2 = "2026-04-09T12:00:00"
104
105 # Sign with ts1.
106 payload_t1 = provenance_payload(commit_id, committed_at=ts1)
107 sig = sign_commit_ed25519(payload_t1, key)
108
109 # Verify with ts2 → must fail.
110 payload_t2 = provenance_payload(commit_id, committed_at=ts2)
111 pub = _pub_bytes(key)
112 assert not verify_commit_ed25519(payload_t2, sig, pub)
113
114 def test_signature_validates_with_same_timestamp(self) -> None:
115 """Sign and verify with the same timestamp → succeeds."""
116 key = _gen_key()
117 commit_id = "g" * 64
118 ts = "2026-04-08T12:00:00"
119 payload = provenance_payload(commit_id, committed_at=ts)
120 sig = sign_commit_ed25519(payload, key)
121 assert verify_commit_ed25519(payload, sig, _pub_bytes(key))
122
123 def test_round_trip_sign_verify_all_fields(self) -> None:
124 """sign_commit_record + verify_commit_ed25519 with all fields → passes."""
125 key = _gen_key()
126 commit_id = "h" * 64
127 agent_id = "test-agent"
128 model_id = "claude-sonnet-4-6"
129 toolchain_id = "agentception/v1"
130 prompt_hash = "ab" * 32
131 ts = "2026-04-08T15:30:00+00:00"
132
133 result = sign_commit_record(
134 commit_id,
135 agent_id,
136 key,
137 model_id=model_id,
138 toolchain_id=toolchain_id,
139 prompt_hash=prompt_hash,
140 committed_at=ts,
141 )
142 assert result is not None
143 sig, pub_b64, _ = result
144
145 pub_bytes = base64.urlsafe_b64decode(pub_b64 + "=")
146 payload = provenance_payload(
147 commit_id,
148 agent_id=agent_id,
149 model_id=model_id,
150 toolchain_id=toolchain_id,
151 prompt_hash=prompt_hash,
152 committed_at=ts,
153 )
154 assert verify_commit_ed25519(payload, sig, pub_bytes)
155
156 def test_sign_commit_record_without_committed_at_still_verifiable(self) -> None:
157 """sign_commit_record with empty committed_at → verifiable with empty committed_at."""
158 key = _gen_key()
159 commit_id = "i" * 64
160 result = sign_commit_record(commit_id, "agent", key)
161 assert result is not None
162 sig, pub_b64, _ = result
163 pub_bytes = base64.urlsafe_b64decode(pub_b64 + "=")
164 # Verify with the same default empty committed_at.
165 payload = provenance_payload(commit_id, agent_id="agent")
166 assert verify_commit_ed25519(payload, sig, pub_bytes)
File History 4 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 28 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 28 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e Merge branch 'dev' into main Human 31 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 50 days ago