1use crate::error::{HostError, Result};
2use crate::proof::{hash_app_bin, hash_input_words, Proof, RealProof};
3use crate::prover::ProverLevel;
4use crate::security::SecurityLevel;
5use crate::vk::{
6 compute_unified_vk, compute_unrolled_vk, verify_proof, verify_unrolled_proof, UnifiedVk,
7 UnrolledVk,
8};
9use airbender_core::guest::Commit;
10use std::path::{Path, PathBuf};
11
12#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
14pub enum VerificationKey {
15 Dev(DevVerificationKey),
16 RealUnified(RealUnifiedVerificationKey),
17 RealUnrolled(RealUnrolledVerificationKey),
18}
19
20impl VerificationKey {
21 pub fn security(&self) -> SecurityLevel {
22 match self {
23 Self::Dev(vk) => vk.security,
24 Self::RealUnified(vk) => vk.vk.security,
25 Self::RealUnrolled(vk) => vk.vk.security,
26 }
27 }
28}
29
30#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
32pub struct DevVerificationKey {
33 pub security: SecurityLevel,
34 pub app_bin_hash: [u8; 32],
35}
36
37#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
39pub struct RealUnifiedVerificationKey {
40 pub vk: UnifiedVk,
41}
42
43#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
45pub struct RealUnrolledVerificationKey {
46 pub level: ProverLevel,
47 pub vk: UnrolledVk,
48}
49
50#[derive(Clone, Copy, Default)]
52pub struct VerificationRequest<'a> {
53 expected_output: Option<&'a dyn Commit>,
54 expected_input_words: Option<&'a [u32]>,
55}
56
57impl<'a> VerificationRequest<'a> {
58 pub fn empty() -> Self {
59 Self::default()
60 }
61
62 pub fn with_expected_output(mut self, expected_output: &'a dyn Commit) -> Self {
63 self.expected_output = Some(expected_output);
64 self
65 }
66
67 pub fn with_expected_input_words(mut self, expected_input_words: &'a [u32]) -> Self {
68 self.expected_input_words = Some(expected_input_words);
69 self
70 }
71
72 pub fn real(expected_output: &'a dyn Commit) -> Self {
73 Self::empty().with_expected_output(expected_output)
74 }
75
76 pub fn dev(expected_input_words: &'a [u32], expected_output: &'a dyn Commit) -> Self {
77 Self::empty()
78 .with_expected_input_words(expected_input_words)
79 .with_expected_output(expected_output)
80 }
81
82 fn expected_output(self) -> Option<&'a dyn Commit> {
83 self.expected_output
84 }
85
86 fn expected_input_words(self) -> Option<&'a [u32]> {
87 self.expected_input_words
88 }
89}
90
91pub trait Verifier {
93 fn generate_vk(&self, security: SecurityLevel) -> Result<VerificationKey>;
94
95 fn verify(
96 &self,
97 proof: &Proof,
98 vk: &VerificationKey,
99 request: VerificationRequest<'_>,
100 ) -> Result<()>;
101}
102
103pub struct DevVerifierBuilder {
105 app_bin_path: PathBuf,
106}
107
108impl DevVerifierBuilder {
109 pub fn new(app_bin_path: impl AsRef<Path>) -> Self {
110 Self {
111 app_bin_path: app_bin_path.as_ref().to_path_buf(),
112 }
113 }
114
115 pub fn build(self) -> Result<DevVerifier> {
116 DevVerifier::new(&self.app_bin_path)
117 }
118}
119
120pub struct RealVerifierBuilder {
122 app_bin_path: PathBuf,
123 level: ProverLevel,
124}
125
126impl RealVerifierBuilder {
127 pub fn new(app_bin_path: impl AsRef<Path>, level: ProverLevel) -> Self {
128 Self {
129 app_bin_path: app_bin_path.as_ref().to_path_buf(),
130 level,
131 }
132 }
133
134 pub fn build(self) -> Result<RealVerifier> {
135 RealVerifier::new(&self.app_bin_path, self.level)
136 }
137}
138
139pub struct DevVerifier {
141 app_bin_hash: [u8; 32],
142}
143
144impl DevVerifier {
145 fn new(app_bin_path: &Path) -> Result<Self> {
146 let app_bin_path = resolve_app_bin_path(app_bin_path)?;
147 let app_bin_hash = hash_app_bin(&app_bin_path)?;
148 Ok(Self { app_bin_hash })
149 }
150
151 pub fn generate_vk(&self, security: SecurityLevel) -> Result<VerificationKey> {
152 Ok(VerificationKey::Dev(DevVerificationKey {
153 security,
154 app_bin_hash: self.app_bin_hash,
155 }))
156 }
157}
158
159impl Verifier for DevVerifier {
160 fn generate_vk(&self, security: SecurityLevel) -> Result<VerificationKey> {
161 DevVerifier::generate_vk(self, security)
162 }
163
164 fn verify(
165 &self,
166 proof: &Proof,
167 vk: &VerificationKey,
168 request: VerificationRequest<'_>,
169 ) -> Result<()> {
170 let proof = match proof {
171 Proof::Dev(proof) => proof,
172 Proof::Real(_) => {
173 return Err(HostError::Verification(
174 "dev verifier cannot verify real proofs".to_string(),
175 ));
176 }
177 };
178 let vk = match vk {
179 VerificationKey::Dev(vk) => vk,
180 VerificationKey::RealUnified(_) | VerificationKey::RealUnrolled(_) => {
181 return Err(HostError::Verification(
182 "dev verifier requires a dev verification key".to_string(),
183 ));
184 }
185 };
186
187 ensure_proof_vk_security_matches(proof.security, vk.security)?;
188
189 if vk.app_bin_hash != self.app_bin_hash {
190 return Err(HostError::Verification(
191 "dev verification key does not match current program".to_string(),
192 ));
193 }
194
195 if proof.app_bin_hash != self.app_bin_hash {
196 return Err(HostError::Verification(
197 "dev proof was produced for a different program".to_string(),
198 ));
199 }
200
201 let expected_input_words = request.expected_input_words().ok_or_else(|| {
202 HostError::Verification("dev verification requires expected input words".to_string())
203 })?;
204 let expected_input_hash = hash_input_words(expected_input_words);
205 if proof.input_words_hash != expected_input_hash {
206 return Err(HostError::Verification(
207 "dev proof input hash does not match expected input words".to_string(),
208 ));
209 }
210
211 let expected_output = request.expected_output().ok_or_else(|| {
212 HostError::Verification("dev verification requires expected output".to_string())
213 })?;
214 let expected_words = expected_output.commit_words();
215 if proof.receipt.output != expected_words {
216 return Err(HostError::Verification(format!(
217 "public output mismatch: expected {expected_words:?}, got {:?}",
218 proof.receipt.output
219 )));
220 }
221
222 Ok(())
223 }
224}
225
226pub struct RealVerifier {
228 app_bin_path: PathBuf,
229 app_bin_hash: [u8; 32],
230 level: ProverLevel,
231}
232
233impl RealVerifier {
234 fn new(app_bin_path: &Path, level: ProverLevel) -> Result<Self> {
235 let app_bin_path = resolve_app_bin_path(app_bin_path)?;
236 let app_bin_hash = hash_app_bin(&app_bin_path)?;
237 Ok(Self {
238 app_bin_path,
239 app_bin_hash,
240 level,
241 })
242 }
243
244 pub fn generate_vk(&self, security: SecurityLevel) -> Result<VerificationKey> {
245 match self.level {
246 ProverLevel::RecursionUnified => {
247 let vk = compute_unified_vk(&self.app_bin_path, security)?;
248 Ok(VerificationKey::RealUnified(RealUnifiedVerificationKey {
249 vk,
250 }))
251 }
252 ProverLevel::Base | ProverLevel::RecursionUnrolled => {
253 let vk = compute_unrolled_vk(&self.app_bin_path, self.level, security)?;
254 Ok(VerificationKey::RealUnrolled(RealUnrolledVerificationKey {
255 level: self.level,
256 vk,
257 }))
258 }
259 }
260 }
261}
262
263impl Verifier for RealVerifier {
264 fn generate_vk(&self, security: SecurityLevel) -> Result<VerificationKey> {
265 RealVerifier::generate_vk(self, security)
266 }
267
268 fn verify(
269 &self,
270 proof: &Proof,
271 vk: &VerificationKey,
272 request: VerificationRequest<'_>,
273 ) -> Result<()> {
274 if request.expected_input_words().is_some() {
275 return Err(HostError::Verification(
276 "real verifier cannot validate input words".to_string(),
277 ));
278 }
279
280 let proof = match proof {
281 Proof::Real(proof) => proof,
282 Proof::Dev(_) => {
283 return Err(HostError::Verification(
284 "real verifier cannot verify dev proofs".to_string(),
285 ));
286 }
287 };
288
289 match (proof.level(), vk) {
290 (
291 ProverLevel::RecursionUnified,
292 VerificationKey::RealUnified(RealUnifiedVerificationKey { vk }),
293 ) => {
294 ensure_proof_vk_security_matches(proof.security(), vk.security)?;
295 verify_proof(
296 proof.inner(),
297 vk,
298 Some(self.app_bin_hash),
299 request.expected_output(),
300 )
301 }
302 (
303 ProverLevel::Base | ProverLevel::RecursionUnrolled,
304 VerificationKey::RealUnrolled(RealUnrolledVerificationKey { level, vk }),
305 ) => {
306 if *level != proof.level() {
307 return Err(HostError::Verification(format!(
308 "proof level {:?} does not match verification key level {:?}",
309 proof.level(),
310 level
311 )));
312 }
313 ensure_proof_vk_security_matches(proof.security(), vk.security)?;
314
315 verify_unrolled_proof(
316 proof.inner(),
317 vk,
318 proof.level(),
319 Some(self.app_bin_hash),
320 request.expected_output(),
321 )
322 }
323 (_, VerificationKey::Dev(_)) => Err(HostError::Verification(
324 "real verifier requires a real verification key".to_string(),
325 )),
326 (ProverLevel::RecursionUnified, VerificationKey::RealUnrolled(_)) => {
327 Err(HostError::Verification(
328 "recursion-unified proofs require unified verification keys".to_string(),
329 ))
330 }
331 (
332 ProverLevel::Base | ProverLevel::RecursionUnrolled,
333 VerificationKey::RealUnified(_),
334 ) => Err(HostError::Verification(
335 "base and recursion-unrolled proofs require unrolled verification keys".to_string(),
336 )),
337 }
338 }
339}
340
341pub fn verify_real_proof_with_vk(
346 proof: &RealProof,
347 vk: &VerificationKey,
348 expected_output: Option<&dyn Commit>,
349) -> Result<()> {
350 match (proof.level(), vk) {
351 (
352 ProverLevel::RecursionUnified,
353 VerificationKey::RealUnified(RealUnifiedVerificationKey { vk }),
354 ) => {
355 ensure_proof_vk_security_matches(proof.security(), vk.security)?;
356 verify_proof(proof.inner(), vk, None, expected_output)
357 }
358 (
359 ProverLevel::Base | ProverLevel::RecursionUnrolled,
360 VerificationKey::RealUnrolled(RealUnrolledVerificationKey { level, vk }),
361 ) => {
362 if *level != proof.level() {
363 return Err(HostError::Verification(format!(
364 "proof level {:?} does not match verification key level {:?}",
365 proof.level(),
366 level
367 )));
368 }
369 ensure_proof_vk_security_matches(proof.security(), vk.security)?;
370
371 verify_unrolled_proof(proof.inner(), vk, proof.level(), None, expected_output)
372 }
373 (_, VerificationKey::Dev(_)) => Err(HostError::Verification(
374 "real proofs require real verification keys".to_string(),
375 )),
376 (ProverLevel::RecursionUnified, VerificationKey::RealUnrolled(_)) => {
377 Err(HostError::Verification(
378 "recursion-unified proof requires a unified verification key".to_string(),
379 ))
380 }
381 (ProverLevel::Base | ProverLevel::RecursionUnrolled, VerificationKey::RealUnified(_)) => {
382 Err(HostError::Verification(
383 "base/recursion-unrolled proof requires an unrolled verification key".to_string(),
384 ))
385 }
386 }
387}
388
389fn ensure_proof_vk_security_matches(
390 proof_security: SecurityLevel,
391 vk_security: SecurityLevel,
392) -> Result<()> {
393 if proof_security != vk_security {
394 return Err(HostError::Verification(format!(
395 "proof security {} bits does not match verification key security {} bits",
396 proof_security, vk_security
397 )));
398 }
399 Ok(())
400}
401
402fn resolve_app_bin_path(path: &Path) -> Result<PathBuf> {
403 if path.exists() {
404 return path.canonicalize().map_err(|err| {
405 HostError::Verification(format!(
406 "failed to canonicalize binary path {}: {err}",
407 path.display()
408 ))
409 });
410 }
411
412 let mut candidate = path.to_path_buf();
413 candidate.set_extension("bin");
414 if candidate.exists() {
415 return candidate.canonicalize().map_err(|err| {
416 HostError::Verification(format!(
417 "failed to canonicalize binary path {}: {err}",
418 candidate.display()
419 ))
420 });
421 }
422
423 Err(HostError::Verification(format!(
424 "binary not found: {}",
425 path.display()
426 )))
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use crate::proof::DevProof;
433 use crate::receipt::Receipt;
434
435 #[test]
436 fn dev_verifier_rejects_security_mismatch() {
437 let app_bin_hash = [7u8; 32];
438 let input_words = [1u32, 10u32];
439 let receipt = receipt_with_output(55);
440 let proof = Proof::Dev(DevProof {
441 security: SecurityLevel::Bits100,
442 app_bin_hash,
443 input_words_hash: hash_input_words(&input_words),
444 receipt,
445 cycles: 100,
446 });
447 let vk = VerificationKey::Dev(DevVerificationKey {
448 security: SecurityLevel::Bits80,
449 app_bin_hash,
450 });
451 let verifier = DevVerifier { app_bin_hash };
452
453 let err = verifier
454 .verify(&proof, &vk, VerificationRequest::dev(&input_words, &55u32))
455 .expect_err("mismatched dev proof/VK security must fail verification");
456
457 assert!(err.to_string().contains("proof security 100 bits"));
458 }
459
460 fn receipt_with_output(output: u32) -> Receipt {
461 let mut registers = [0u32; 32];
462 registers[10] = output;
463 Receipt::from_registers(registers)
464 }
465}