1
//! Verifier implementation for v1 client puzzles
2

            
3
use crate::pow::err::Error;
4
use crate::pow::v1::challenge::Challenge;
5
use crate::pow::v1::{err::RuntimeErrorV1, err::SolutionErrorV1, types::Instance, types::Solution};
6
use equix::{EquiXBuilder, HashError, RuntimeOption};
7

            
8
use super::Seed;
9

            
10
/// Checker for potential [`Solution`]s to a particular puzzle [`Instance`]
11
///
12
/// Holds information about the puzzle instance, and optional configuration
13
/// settings.
14
pub struct Verifier {
15
    /// The puzzle instance we're verifying
16
    instance: Instance,
17
    /// Configuration settings for Equi-X, as an [`EquiXBuilder`] instance
18
    equix: EquiXBuilder,
19
}
20

            
21
impl Verifier {
22
    /// Construct a new [`Verifier`] by wrapping an [`Instance`].
23
224
    pub fn new(instance: Instance) -> Self {
24
224
        Self {
25
224
            instance,
26
224
            equix: Default::default(),
27
224
        }
28
224
    }
29

            
30
    /// Construct a new [`Verifier`], explicitly taking a [`EquiXBuilder`].
31
32
    pub fn new_with_equix(instance: Instance, equix: EquiXBuilder) -> Self {
32
32
        Self { instance, equix }
33
32
    }
34

            
35
    /// Select the HashX runtime to use for this verifier.
36
    ///
37
    /// By default, uses [`RuntimeOption::TryCompile`]
38
    pub fn runtime(&mut self, option: RuntimeOption) -> &mut Self {
39
        self.equix.runtime(option);
40
        self
41
    }
42

            
43
    /// Check whether a solution is valid for this puzzle instance.
44
    ///
45
    /// May return a [`SolutionErrorV1`] or a [`RuntimeErrorV1`]
46
224
    pub fn check(&self, solution: &Solution) -> Result<(), Error> {
47
224
        match self.check_seed(solution) {
48
8
            Err(e) => Err(Error::BadSolution(e.into())),
49
            Ok(()) => {
50
216
                let challenge = Challenge::new(&self.instance, solution.effort(), solution.nonce());
51
216
                match challenge.check_effort(&solution.proof_to_bytes()) {
52
24
                    Err(e) => Err(Error::BadSolution(e.into())),
53
192
                    Ok(()) => match self.equix.verify(challenge.as_ref(), solution.proof()) {
54
184
                        Ok(()) => Ok(()),
55
                        Err(equix::Error::HashSum) => {
56
8
                            Err(Error::BadSolution(SolutionErrorV1::HashSum.into()))
57
                        }
58
                        Err(equix::Error::Hash(HashError::ProgramConstraints)) => Err(
59
                            Error::BadSolution(SolutionErrorV1::ChallengeConstraints.into()),
60
                        ),
61
                        Err(e) => Err(Error::VerifyRuntime(RuntimeErrorV1::EquiX(e).into())),
62
                    },
63
                }
64
            }
65
        }
66
224
    }
67

            
68
    /// Check the [`super::SeedHead`] of a solution against an [`Instance`].
69
    ///
70
    /// This is a very cheap test, this should come first so a service
71
    /// can verify every [`Solution`] against its last two [`Instance`]s.
72
224
    fn check_seed(&self, solution: &Solution) -> Result<(), SolutionErrorV1> {
73
224
        if solution.seed_head() == self.instance.seed().head() {
74
216
            Ok(())
75
        } else {
76
8
            Err(SolutionErrorV1::Seed)
77
        }
78
224
    }
79

            
80
    /// Return the seed for this verifier's instance.
81
    pub fn seed(&self) -> &Seed {
82
        self.instance.seed()
83
    }
84
}