logicaffeine_verify/
certificate.rs1use std::collections::hash_map::DefaultHasher;
11use std::hash::{Hash, Hasher};
12
13use crate::ir::VerifyExpr;
14use serde::{Serialize, Deserialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ProofCertificate {
19 pub claim: VerifyClaim,
20 pub steps: Vec<ProofStep>,
21 pub axioms: Vec<String>,
22 pub checkable: bool,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub enum VerifyClaim {
28 Equivalent { description: String },
29 Safe { property: String, bound: Option<u32> },
30 Unsafe { counterexample: String },
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ProofStep {
36 pub rule: String,
37 pub premises: Vec<usize>,
38 pub conclusion: String,
39 pub formula: Option<VerifyExpr>,
41 pub claim_binding: u64,
43}
44
45fn digest_claim(claim: &VerifyClaim) -> u64 {
47 let mut hasher = DefaultHasher::new();
48 match claim {
49 VerifyClaim::Equivalent { description } => {
50 "Equivalent".hash(&mut hasher);
51 description.hash(&mut hasher);
52 }
53 VerifyClaim::Safe { property, bound } => {
54 "Safe".hash(&mut hasher);
55 property.hash(&mut hasher);
56 bound.hash(&mut hasher);
57 }
58 VerifyClaim::Unsafe { counterexample } => {
59 "Unsafe".hash(&mut hasher);
60 counterexample.hash(&mut hasher);
61 }
62 }
63 hasher.finish()
64}
65
66pub fn digest_formula(expr: &VerifyExpr) -> u64 {
68 let mut hasher = DefaultHasher::new();
69 let serialized = serde_json::to_string(expr).unwrap_or_default();
70 serialized.hash(&mut hasher);
71 hasher.finish()
72}
73
74fn expected_conclusion_keyword(claim: &VerifyClaim) -> &'static str {
76 match claim {
77 VerifyClaim::Equivalent { .. } => "UNSAT",
78 VerifyClaim::Safe { .. } => "Verified",
79 VerifyClaim::Unsafe { .. } => "SAT",
80 }
81}
82
83pub fn generate_equivalence_certificate(
85 a_desc: &str,
86 b_desc: &str,
87 is_equivalent: bool,
88) -> ProofCertificate {
89 let claim = if is_equivalent {
90 VerifyClaim::Equivalent {
91 description: format!("{} ≡ {}", a_desc, b_desc),
92 }
93 } else {
94 VerifyClaim::Unsafe {
95 counterexample: format!("{} ≢ {}", a_desc, b_desc),
96 }
97 };
98
99 let binding = digest_claim(&claim);
100
101 let xor_formula = VerifyExpr::not(VerifyExpr::iff(
102 VerifyExpr::var(a_desc),
103 VerifyExpr::var(b_desc),
104 ));
105
106 ProofCertificate {
107 claim,
108 steps: vec![ProofStep {
109 rule: "Z3-SAT-check".into(),
110 premises: vec![],
111 conclusion: if is_equivalent {
112 "UNSAT: no distinguishing assignment exists".into()
113 } else {
114 "SAT: distinguishing assignment found".into()
115 },
116 formula: Some(xor_formula),
117 claim_binding: binding,
118 }],
119 axioms: vec!["propositional-logic".into(), "Z3-soundness".into()],
120 checkable: true,
121 }
122}
123
124pub fn generate_safety_certificate(
126 property_desc: &str,
127 k: u32,
128 is_safe: bool,
129) -> ProofCertificate {
130 let claim = if is_safe {
131 VerifyClaim::Safe {
132 property: property_desc.into(),
133 bound: Some(k),
134 }
135 } else {
136 VerifyClaim::Unsafe {
137 counterexample: format!("Counterexample at depth {}", k),
138 }
139 };
140
141 let binding = digest_claim(&claim);
142
143 let safety_formula = VerifyExpr::apply(
144 "G",
145 vec![VerifyExpr::var(property_desc)],
146 );
147
148 ProofCertificate {
149 claim,
150 steps: vec![ProofStep {
151 rule: if is_safe { "k-induction" } else { "BMC" }.into(),
152 premises: vec![],
153 conclusion: format!("Verified at depth k={}", k),
154 formula: Some(safety_formula),
155 claim_binding: binding,
156 }],
157 axioms: vec!["k-induction-soundness".into()],
158 checkable: true,
159 }
160}
161
162pub fn verify_certificate(cert: &ProofCertificate) -> bool {
172 if cert.steps.is_empty() {
173 return false;
174 }
175 if !cert.checkable {
176 return false;
177 }
178
179 let claim_digest = digest_claim(&cert.claim);
180
181 for (i, step) in cert.steps.iter().enumerate() {
182 for &premise in &step.premises {
184 if premise >= i {
185 return false;
186 }
187 }
188
189 if step.claim_binding != 0 && step.claim_binding != claim_digest {
191 return false;
192 }
193
194 if step.claim_binding != 0 && step.formula.is_none() {
197 return false;
198 }
199 }
200
201 let final_step = cert.steps.last().unwrap();
203 let expected_keyword = expected_conclusion_keyword(&cert.claim);
204 if !final_step.conclusion.contains(expected_keyword) {
205 return false;
206 }
207
208 true
209}