1use halo2_base::{
2 gates::circuit::CircuitBuilderStage,
3 halo2_proofs::plonk::{keygen_pk, keygen_vk},
4};
5use openvm_stark_sdk::{
6 config::baby_bear_bn254_poseidon2::BabyBearBn254Poseidon2Config as RootConfig,
7 openvm_stark_backend::proof::Proof,
8};
9#[cfg(feature = "evm-prove")]
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 circuit::StaticVerifierCircuit,
14 config::StaticVerifierShape,
15 prover::{Halo2Params, Halo2ProvingMetadata, Halo2ProvingPinning, StaticVerifierProof},
16 tracegen::graph_executor::GraphProgram,
17};
18
19impl StaticVerifierCircuit {
20 pub fn keygen(
25 &self,
26 params: &Halo2Params,
27 shape: &StaticVerifierShape,
28 representative_proof: &Proof<RootConfig>,
29 ) -> Halo2ProvingPinning {
30 let mut builder = Self::builder(CircuitBuilderStage::Keygen, shape);
31 self.populate(&mut builder, representative_proof);
32
33 let config_params = builder.calculate_params(Some(shape.minimum_rows));
34
35 let vk = keygen_vk(params, &builder).expect("keygen_vk should succeed");
36 let pk = keygen_pk(params, vk, &builder).expect("keygen_pk should succeed");
37 let break_points = builder.break_points();
38
39 Halo2ProvingPinning {
40 pk,
41 metadata: Halo2ProvingMetadata {
42 config_params,
43 break_points,
44 num_pvs: builder
45 .assigned_instances
46 .iter()
47 .map(|instances| instances.len())
48 .collect(),
49 },
50 }
51 }
52}
53
54#[derive(Clone)]
57pub struct StaticVerifierProvingKey {
58 pub circuit: StaticVerifierCircuit,
59 pub pinning: Halo2ProvingPinning,
60 pub shape: StaticVerifierShape,
61 pub graph_program: GraphProgram,
62}
63
64impl StaticVerifierProvingKey {
65 pub fn keygen(
67 params: &Halo2Params,
68 shape: StaticVerifierShape,
69 circuit: StaticVerifierCircuit,
70 representative_proof: &Proof<RootConfig>,
71 ) -> Self {
72 let pinning = circuit.keygen(params, &shape, representative_proof);
73 let graph_program = tracing::info_span!("build_graph_program")
74 .in_scope(|| GraphProgram::new(&circuit, shape.lookup_bits, representative_proof));
75 Self {
76 circuit,
77 pinning,
78 shape,
79 graph_program,
80 }
81 }
82
83 pub fn prove(&self, params: &Halo2Params, proof: &Proof<RootConfig>) -> StaticVerifierProof {
85 self.circuit
86 .prove(params, &self.pinning, &self.shape, proof)
87 }
88
89 pub fn verify(&self, params: &Halo2Params, proof: &StaticVerifierProof) -> bool {
91 StaticVerifierCircuit::verify(params, self.pinning.pk.get_vk(), proof)
92 }
93}
94
95#[cfg(feature = "evm-prove")]
98use halo2_base::{
99 gates::circuit::builder::BaseCircuitBuilder,
100 halo2_proofs::{halo2curves::bn256::Fr, plonk::AdviceColumns},
101};
102#[cfg(feature = "evm-prove")]
103use snark_verifier_sdk::{
104 evm::{gen_evm_proof_shplonk, gen_evm_verifier_sol_code},
105 SHPLONK,
106};
107
108#[cfg(feature = "evm-prove")]
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct RawEvmProof {
112 pub instances: Vec<Fr>,
113 pub proof: Vec<u8>,
114}
115
116#[cfg(feature = "evm-prove")]
117impl StaticVerifierProvingKey {
118 pub fn generate_fallback_evm_verifier(&self, params: &Halo2Params) -> String {
120 gen_evm_verifier_sol_code::<BaseCircuitBuilder<Fr>, SHPLONK>(
121 params,
122 self.pinning.pk.get_vk(),
123 self.pinning.metadata.num_pvs.clone(),
124 )
125 }
126
127 pub fn prove_wrapped(
137 &self,
138 params: &Halo2Params,
139 proof: &Proof<RootConfig>,
140 state: &mut crate::tracegen::graph_executor::GraphExecutorState,
141 ) -> snark_verifier_sdk::Snark {
142 static GRAPH_EXE_THREADS: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
145 let num_threads = *GRAPH_EXE_THREADS.get_or_init(|| {
146 std::env::var("GRAPH_EXE_THREADS")
147 .ok()
148 .and_then(|s| s.parse().ok())
149 .filter(|&t: &usize| t > 0)
150 .unwrap_or_else(|| {
151 std::thread::available_parallelism()
152 .map(|n| n.get().saturating_sub(2).max(1))
153 .unwrap_or(1)
154 })
155 });
156
157 let (advice, instances) = self.generate_witness(proof, num_threads, state);
158
159 snark_verifier_sdk::halo2::gen_snark_from_base(params, &self.pinning.pk, advice, instances)
160 }
161
162 pub fn generate_witness(
168 &self,
169 proof: &Proof<RootConfig>,
170 num_threads: usize,
171 state: &mut crate::tracegen::graph_executor::GraphExecutorState,
172 ) -> (AdviceColumns<Fr>, Vec<Vec<Fr>>) {
173 use halo2_base::{
174 gates::circuit::MaybeRangeConfig,
175 halo2_proofs::plonk::{Circuit, ConstraintSystem},
176 };
177 use tracing::info_span;
178
179 use crate::{
180 stages::full_pipeline::load_proof_wire,
181 tracegen::graph_executor::{FusedColumnBuilder, GraphExecutor},
182 };
183
184 let num_advice_columns = self.pinning.pk.get_vk().cs().num_advice_columns();
186 let n = 1usize << self.pinning.metadata.config_params.k;
187 let mut cs = ConstraintSystem::<Fr>::default();
188 let config = <BaseCircuitBuilder<Fr> as Circuit<Fr>>::configure_with_params(
189 &mut cs,
190 self.pinning.metadata.config_params.clone(),
191 );
192 let MaybeRangeConfig::WithRange(range_config) = &config.base else {
193 panic!("static verifier requires lookup advice columns");
194 };
195 let lookup_col_indices: Vec<usize> = range_config.lookup_advice[0]
196 .iter()
197 .map(|c| c.index())
198 .collect();
199 assert!(
200 !lookup_col_indices.is_empty(),
201 "range lookups require lookup advice columns"
202 );
203 let max_lookup_rows = range_config.gate.max_rows;
204 let break_points = self.pinning.metadata.break_points[0].clone();
205 assert!(
206 self.graph_program
207 .lookup_cells()
208 .div_ceil(lookup_col_indices.len())
209 <= max_lookup_rows,
210 "range lookups would be assigned to unusable rows"
211 );
212
213 let mut builder =
214 FusedColumnBuilder::new(n, num_advice_columns, break_points, lookup_col_indices);
215
216 let mut executor = GraphExecutor::new(&self.graph_program, state);
217 executor.state.reset();
218 info_span!("populate_inputs").in_scope(|| {
219 load_proof_wire(&mut executor, proof, &self.circuit.log_heights_per_air);
220 });
221 info_span!("graph_witness_gen", num_threads).in_scope(|| {
222 executor.run(
223 num_threads,
224 |advice_offset, advice_delta, lookup_offset, lookup_delta| {
225 builder.append(advice_offset, advice_delta, lookup_offset, lookup_delta)
226 },
227 );
228 });
229 let pvs = info_span!("collect_pvs").in_scope(|| {
230 let advice = executor.advice();
231 self.graph_program
232 .pv_offsets()
233 .iter()
234 .map(|&offset| advice[offset])
235 .collect()
236 });
237
238 let mut instances = vec![Vec::new(); self.shape.instance_columns];
239 instances[0] = pvs;
240
241 let advice = builder.take_columns();
242 (advice, instances)
243 }
244
245 pub fn generate_dummy_snark(
247 &self,
248 reader: &impl crate::wrapper::Halo2ParamsReader,
249 ) -> snark_verifier_sdk::Snark {
250 let k = self.pinning.metadata.config_params.k;
251 let params = reader.read_params(k);
252 snark_verifier_sdk::halo2::gen_dummy_snark_from_vk::<SHPLONK>(
253 ¶ms,
254 self.pinning.pk.get_vk(),
255 self.pinning.metadata.num_pvs.clone(),
256 None,
257 )
258 }
259
260 pub fn prove_for_evm_unwrapped(
262 &self,
263 params: &Halo2Params,
264 proof: &Proof<RootConfig>,
265 ) -> RawEvmProof {
266 self.shape.assert_onchain_verifier_supported();
267
268 let mut builder = BaseCircuitBuilder::prover(
269 self.pinning.metadata.config_params.clone(),
270 self.pinning.metadata.break_points.clone(),
271 )
272 .use_instance_columns(self.shape.instance_columns);
273
274 let public_inputs = self.circuit.populate(&mut builder, proof);
275 let instances_vec = public_inputs.to_vec();
276
277 let snark = gen_evm_proof_shplonk(
278 params,
279 &self.pinning.pk,
280 builder,
281 vec![instances_vec.clone()],
282 );
283
284 RawEvmProof {
285 instances: instances_vec,
286 proof: snark,
287 }
288 }
289}
290
291#[cfg(feature = "evm-verify")]
295pub fn evm_verify(deployment_code: &[u8], proof: &RawEvmProof) -> Result<u64, String> {
296 snark_verifier_sdk::evm::evm_verify(
297 deployment_code.to_vec(),
298 vec![proof.instances.clone()],
299 proof.proof.clone(),
300 )
301 .map_err(|e| format!("EVM verification failed: {e}"))
302}