Skip to main content

airbender_host/
verifier.rs

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/// Wrapper around all verification-key flavors.
13#[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/// Development verification key.
31#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
32pub struct DevVerificationKey {
33    pub security: SecurityLevel,
34    pub app_bin_hash: [u8; 32],
35}
36
37/// Unified (recursion) verification key wrapper.
38#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
39pub struct RealUnifiedVerificationKey {
40    pub vk: UnifiedVk,
41}
42
43/// Unrolled (base / recursion-unrolled) verification key wrapper.
44#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
45pub struct RealUnrolledVerificationKey {
46    pub level: ProverLevel,
47    pub vk: UnrolledVk,
48}
49
50/// Verification checks requested by the caller.
51#[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
91/// Verifier interface shared by dev and real verifiers.
92pub 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
103/// Builder for a development verifier.
104pub 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
120/// Builder for a real verifier.
121pub 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
139/// Development verifier implementation.
140pub 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
226/// Real verifier implementation.
227pub 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
341/// Verify a real proof envelope against a real verification key.
342///
343/// This helper validates proof/VK compatibility and optional expected public output.
344/// It intentionally does not enforce app.bin hash checks.
345pub 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}