Skip to content

Commit 3ee059f

Browse files
committed
test: add receipt chain integrity verifier
1 parent 1d9b991 commit 3ee059f

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import copy
2+
import hashlib
3+
import json
4+
from pathlib import Path
5+
6+
7+
FIXTURE_PATH = Path(__file__).resolve().parents[1] / "examples" / "refusal_receipt_chain_v0.2.json"
8+
SHA_PREFIX = "sha256:"
9+
10+
11+
def canonical_sha256(value):
12+
"""Return sha256 over deterministic JSON with sorted keys and compact separators."""
13+
canonical = json.dumps(value, sort_keys=True, separators=(",", ":"))
14+
return f"{SHA_PREFIX}{hashlib.sha256(canonical.encode('utf-8')).hexdigest()}"
15+
16+
17+
def receipt_body_for_hash(receipt):
18+
"""receipt_hash excludes receipt_hash and signature."""
19+
body = copy.deepcopy(receipt)
20+
body.pop("receipt_hash", None)
21+
body.pop("signature", None)
22+
return body
23+
24+
25+
def load_fixture():
26+
with FIXTURE_PATH.open("r", encoding="utf-8") as fixture:
27+
return json.load(fixture)
28+
29+
30+
def test_valid_receipt_chain_hashes_recompute():
31+
fixture = load_fixture()
32+
chain = fixture["valid_chain"]
33+
34+
payloads = [
35+
fixture["attempted_payloads"]["payload_0001"],
36+
fixture["attempted_payloads"]["payload_0002"],
37+
]
38+
decision_records = [
39+
fixture["decision_records"]["dec_0001"],
40+
fixture["decision_records"]["dec_0002"],
41+
]
42+
state_snapshots = [
43+
fixture["post_refusal_state_snapshots"]["state_after_rcpt_0001"],
44+
fixture["post_refusal_state_snapshots"]["state_after_rcpt_0002"],
45+
]
46+
47+
for index, receipt in enumerate(chain):
48+
assert receipt["decision"] == "REFUSE"
49+
assert receipt["mutation_committed"] is False
50+
assert receipt["payload_hash"] == canonical_sha256(payloads[index])
51+
assert receipt["decision_record_hash"] == canonical_sha256(decision_records[index])
52+
assert receipt["state_snapshot_hash"] == canonical_sha256(state_snapshots[index])
53+
assert receipt["receipt_hash"] == canonical_sha256(receipt_body_for_hash(receipt))
54+
55+
56+
def test_receipt_chain_links_to_previous_receipt_hash():
57+
fixture = load_fixture()
58+
chain = fixture["valid_chain"]
59+
60+
assert chain[0]["previous_receipt_hash"] == fixture["genesis_previous_receipt_hash"]
61+
assert chain[1]["previous_receipt_hash"] == chain[0]["receipt_hash"]
62+
63+
64+
def test_broken_previous_receipt_hash_is_rejected():
65+
fixture = load_fixture()
66+
chain = fixture["valid_chain"]
67+
broken_receipt = fixture["broken_chain_examples"][0]["receipt"]
68+
69+
assert broken_receipt["previous_receipt_hash"] != chain[0]["receipt_hash"]

0 commit comments

Comments
 (0)