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#[derive(Debug)]
58#[non_exhaustive]
59pub enum ProveError {
60 KeygenVk(plonk::Error),
62 KeygenPk(plonk::Error),
64 CachedKeygen(String),
66 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(¶ms, &empty_circuit).expect("tiny keygen_vk should succeed");
160 let pk =
161 plonk::keygen_pk(¶ms, vk, &empty_circuit).expect("tiny keygen_pk should succeed");
162 let public_inputs = [vesta::Scalar::from(1)];
163
164 let err = create_proof_bytes(¶ms, &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(¶ms, &empty_circuit).expect("tiny keygen_vk should succeed");
176 let pk = plonk::keygen_pk(¶ms, 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(¶ms, &pk, circuit, &public_inputs)
183 .expect("tiny proof should succeed");
184
185 verify_proof_bytes("tiny", ¶ms, &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 ¶ms,
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}