src/pqc_training_data/verifier.py
2.8 KB · 83 lines · python Raw
1 """End-to-end verifier: signature + inclusion proof."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6
7 from pqc_training_data.commitment import CommitmentSigner, TrainingCommitment
8 from pqc_training_data.errors import CommitmentVerificationError
9 from pqc_training_data.merkle import InclusionProof, MerkleTree
10 from pqc_training_data.record import DataRecord
11
12
13 @dataclass(frozen=True)
14 class VerificationResult:
15 signature_valid: bool
16 proof_valid: bool
17 leaf_matches_record: bool
18 commitment_id: str
19 record_leaf_hash: str | None
20 claimed_root: str
21 error: str | None = None
22
23 @property
24 def fully_verified(self) -> bool:
25 return self.signature_valid and self.proof_valid and self.leaf_matches_record
26
27
28 class CommitmentVerifier:
29 """Verify that (record, proof, commitment) are mutually consistent.
30
31 Use cases:
32 - 'Prove this document was in your training set' - caller supplies
33 record + proof + commitment; verifier returns True/False.
34 - 'Prove this document was NOT in your training set' - impossible in
35 general from a Merkle commitment alone (would require a separate
36 sorted-tree construction). This verifier handles positive proofs only.
37 """
38
39 @staticmethod
40 def verify(
41 record: DataRecord,
42 proof: InclusionProof,
43 commitment: TrainingCommitment,
44 ) -> VerificationResult:
45 sig_valid = CommitmentSigner.verify(commitment)
46 proof_valid = MerkleTree.verify_inclusion(proof)
47 expected_leaf = record.leaf_hash().hex
48 leaf_ok = expected_leaf == proof.leaf_hash
49 root_ok = proof.root == commitment.root
50
51 err: str | None = None
52 if not sig_valid:
53 err = "commitment signature invalid"
54 elif not proof_valid:
55 err = "inclusion proof failed to verify"
56 elif not leaf_ok:
57 err = (
58 f"record leaf_hash {expected_leaf[:16]}... does not match proof "
59 f"{proof.leaf_hash[:16]}..."
60 )
61 elif not root_ok:
62 err = "proof root does not match commitment root"
63
64 return VerificationResult(
65 signature_valid=sig_valid,
66 proof_valid=proof_valid and root_ok,
67 leaf_matches_record=leaf_ok,
68 commitment_id=commitment.commitment_id,
69 record_leaf_hash=expected_leaf,
70 claimed_root=commitment.root,
71 error=err,
72 )
73
74 @staticmethod
75 def verify_or_raise(
76 record: DataRecord,
77 proof: InclusionProof,
78 commitment: TrainingCommitment,
79 ) -> None:
80 result = CommitmentVerifier.verify(record, proof, commitment)
81 if not result.fully_verified:
82 raise CommitmentVerificationError(result.error or "verification failed")
83