Skip to main content

voting_circuits/
prove_error.rs

1use halo2_proofs::{
2    pasta::EqAffine,
3    plonk::{self, create_proof, verify_proof, SingleVerifier},
4    poly::commitment::Params,
5    transcript::{Blake2bRead, Blake2bWrite, Challenge255},
6};
7use pasta_curves::vesta;
8use rand::rngs::OsRng;
9use std::vec::Vec;
10
11pub(crate) fn create_proof_bytes<ConcreteCircuit>(
12    params: &Params<EqAffine>,
13    pk: &plonk::ProvingKey<EqAffine>,
14    circuit: ConcreteCircuit,
15    public_inputs: &[vesta::Scalar],
16) -> Result<Vec<u8>, ProveError>
17where
18    ConcreteCircuit: plonk::Circuit<vesta::Scalar>,
19{
20    let mut transcript = Blake2bWrite::<_, EqAffine, Challenge255<_>>::init(vec![]);
21    create_proof(
22        params,
23        pk,
24        &[circuit],
25        &[&[public_inputs]],
26        OsRng,
27        &mut transcript,
28    )?;
29    Ok(transcript.finalize())
30}
31
32pub(crate) fn verify_proof_bytes(
33    label: &str,
34    params: &Params<EqAffine>,
35    vk: &plonk::VerifyingKey<EqAffine>,
36    proof: &[u8],
37    public_inputs: &[vesta::Scalar],
38) -> Result<(), String> {
39    let strategy = SingleVerifier::new(params);
40    let mut proof_reader = proof;
41    let mut transcript = Blake2bRead::<_, EqAffine, Challenge255<_>>::init(&mut proof_reader);
42
43    verify_proof(params, vk, strategy, &[&[public_inputs]], &mut transcript)
44        .map_err(|e| format!("{label} verification failed: {:?}", e))?;
45
46    if !proof_reader.is_empty() {
47        return Err(format!(
48            "{label} verification failed: proof has {} trailing unread bytes",
49            proof_reader.len()
50        ));
51    }
52
53    Ok(())
54}
55
56/// Error returned when Halo2 proof creation fails.
57#[derive(Debug)]
58#[non_exhaustive]
59pub enum ProveError {
60    /// Halo2 failed while generating a verifying key.
61    KeygenVk(plonk::Error),
62    /// Halo2 failed while generating a proving key.
63    KeygenPk(plonk::Error),
64    /// Cached key generation previously failed.
65    CachedKeygen(String),
66    /// Halo2 rejected the proof inputs or failed during synthesis.
67    Halo2(plonk::Error),
68}
69
70impl From<plonk::Error> for ProveError {
71    fn from(error: plonk::Error) -> Self {
72        ProveError::Halo2(error)
73    }
74}
75
76impl core::fmt::Display for ProveError {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        match self {
79            ProveError::KeygenVk(error) => {
80                write!(f, "Halo2 verifying key generation failed: {error}")
81            }
82            ProveError::KeygenPk(error) => {
83                write!(f, "Halo2 proving key generation failed: {error}")
84            }
85            ProveError::CachedKeygen(error) => write!(f, "Halo2 key generation failed: {error}"),
86            ProveError::Halo2(error) => write!(f, "Halo2 proof creation failed: {error}"),
87        }
88    }
89}
90
91impl std::error::Error for ProveError {
92    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
93        match self {
94            ProveError::KeygenVk(error) => Some(error),
95            ProveError::KeygenPk(error) => Some(error),
96            ProveError::CachedKeygen(_) => None,
97            ProveError::Halo2(error) => Some(error),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use halo2_proofs::{
106        circuit::{Layouter, SimpleFloorPlanner, Value},
107        plonk::{Advice, Column, ConstraintSystem, Error as PlonkError, Instance},
108    };
109
110    #[derive(Clone, Debug)]
111    struct TinyCircuit {
112        witness: Value<vesta::Scalar>,
113    }
114
115    #[derive(Clone, Debug)]
116    struct TinyConfig {
117        advice: Column<Advice>,
118        instance: Column<Instance>,
119    }
120
121    impl plonk::Circuit<vesta::Scalar> for TinyCircuit {
122        type Config = TinyConfig;
123        type FloorPlanner = SimpleFloorPlanner;
124
125        fn without_witnesses(&self) -> Self {
126            Self {
127                witness: Value::unknown(),
128            }
129        }
130
131        fn configure(meta: &mut ConstraintSystem<vesta::Scalar>) -> Self::Config {
132            let advice = meta.advice_column();
133            let instance = meta.instance_column();
134            meta.enable_equality(advice);
135            meta.enable_equality(instance);
136            TinyConfig { advice, instance }
137        }
138
139        fn synthesize(
140            &self,
141            config: Self::Config,
142            mut layouter: impl Layouter<vesta::Scalar>,
143        ) -> Result<(), PlonkError> {
144            let cell = layouter.assign_region(
145                || "witness",
146                |mut region| region.assign_advice(|| "witness", config.advice, 0, || self.witness),
147            )?;
148
149            layouter.constrain_instance(cell.cell(), config.instance, 0)
150        }
151    }
152
153    #[test]
154    fn create_proof_bytes_returns_err_for_missing_witness() {
155        let params = Params::<EqAffine>::new(4);
156        let empty_circuit = TinyCircuit {
157            witness: Value::unknown(),
158        };
159        let vk = plonk::keygen_vk(&params, &empty_circuit).expect("tiny keygen_vk should succeed");
160        let pk =
161            plonk::keygen_pk(&params, vk, &empty_circuit).expect("tiny keygen_pk should succeed");
162        let public_inputs = [vesta::Scalar::from(1)];
163
164        let err = create_proof_bytes(&params, &pk, empty_circuit, &public_inputs).unwrap_err();
165
166        assert!(matches!(err, ProveError::Halo2(plonk::Error::Synthesis)));
167    }
168
169    #[test]
170    fn verify_proof_bytes_rejects_trailing_unread_bytes() {
171        let params = Params::<EqAffine>::new(4);
172        let empty_circuit = TinyCircuit {
173            witness: Value::unknown(),
174        };
175        let vk = plonk::keygen_vk(&params, &empty_circuit).expect("tiny keygen_vk should succeed");
176        let pk = plonk::keygen_pk(&params, vk.clone(), &empty_circuit)
177            .expect("tiny keygen_pk should succeed");
178        let public_inputs = [vesta::Scalar::from(1)];
179        let circuit = TinyCircuit {
180            witness: Value::known(public_inputs[0]),
181        };
182        let proof = create_proof_bytes(&params, &pk, circuit, &public_inputs)
183            .expect("tiny proof should succeed");
184
185        verify_proof_bytes("tiny", &params, &vk, &proof, &public_inputs)
186            .expect("canonical proof should verify");
187
188        let mut proof_with_trailing_bytes = proof;
189        proof_with_trailing_bytes.extend_from_slice(b"junk");
190
191        let err = verify_proof_bytes(
192            "tiny",
193            &params,
194            &vk,
195            &proof_with_trailing_bytes,
196            &public_inputs,
197        )
198        .expect_err("proof with trailing bytes must be rejected");
199
200        assert!(
201            err.contains("4 trailing unread bytes"),
202            "unexpected error: {err}"
203        );
204    }
205}