tests/test_proof.py
| 1 | """Tests for ReasoningProver inclusion proofs.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import pytest |
| 6 | |
| 7 | from pqc_reasoning_ledger import ( |
| 8 | ReasoningProver, |
| 9 | ReasoningRecorder, |
| 10 | StepNotFoundError, |
| 11 | ) |
| 12 | |
| 13 | |
| 14 | def _build_sealed(recorder: ReasoningRecorder): |
| 15 | recorder.record_observation("step 1") |
| 16 | recorder.record_hypothesis("step 2") |
| 17 | recorder.record_deduction("step 3") |
| 18 | recorder.record_self_critique("step 4") |
| 19 | recorder.record_decision("step 5") |
| 20 | return recorder.seal() |
| 21 | |
| 22 | |
| 23 | def test_prove_step_existing_returns_proof( |
| 24 | sample_trace_started: ReasoningRecorder, |
| 25 | ) -> None: |
| 26 | sealed = _build_sealed(sample_trace_started) |
| 27 | target = sealed.steps[2] |
| 28 | proof = ReasoningProver.prove_step(sealed, target.step_id) |
| 29 | assert proof.step.step_id == target.step_id |
| 30 | assert proof.trace_id == sealed.metadata.trace_id |
| 31 | assert proof.merkle_root == sealed.merkle_root |
| 32 | assert proof.proof.index == 2 |
| 33 | assert proof.proof.leaf_hash == target.step_hash |
| 34 | |
| 35 | |
| 36 | def test_verify_proof_passes( |
| 37 | sample_trace_started: ReasoningRecorder, |
| 38 | ) -> None: |
| 39 | sealed = _build_sealed(sample_trace_started) |
| 40 | for idx in range(len(sealed.steps)): |
| 41 | target = sealed.steps[idx] |
| 42 | proof = ReasoningProver.prove_step(sealed, target.step_id) |
| 43 | assert ReasoningProver.verify_proof(proof) is True |
| 44 | |
| 45 | |
| 46 | def test_prove_step_missing_raises( |
| 47 | sample_trace_started: ReasoningRecorder, |
| 48 | ) -> None: |
| 49 | sealed = _build_sealed(sample_trace_started) |
| 50 | with pytest.raises(StepNotFoundError): |
| 51 | ReasoningProver.prove_step(sealed, "urn:pqc-step:does-not-exist") |
| 52 | |
| 53 | |
| 54 | def test_verify_proof_fails_on_tampered_step( |
| 55 | sample_trace_started: ReasoningRecorder, |
| 56 | ) -> None: |
| 57 | sealed = _build_sealed(sample_trace_started) |
| 58 | target = sealed.steps[1] |
| 59 | proof = ReasoningProver.prove_step(sealed, target.step_id) |
| 60 | # Tamper the step's step_hash on the proof so it disagrees with proof.leaf_hash |
| 61 | proof.step.step_hash = "0" * 64 |
| 62 | assert ReasoningProver.verify_proof(proof) is False |
| 63 | |