1pub use domain::*;
2use openvm_native_compiler::{
3 ir::{
4 Array, ArrayLike, Builder, Config, Ext, ExtensionOperand, Felt, RVar, SymbolicVar, Usize,
5 Var,
6 },
7 prelude::MemVariable,
8};
9use openvm_native_compiler_derive::iter_zip;
10use openvm_stark_backend::p3_field::{FieldAlgebra, TwoAdicField};
11pub use two_adic_pcs::*;
12
13use self::types::{DimensionsVariable, FriConfigVariable, FriQueryProofVariable};
14use crate::{
15 digest::{CanPoseidon2Digest, DigestVariable},
16 outer_poseidon2::Poseidon2CircuitBuilder,
17 utils::cond_eval,
18 vars::{HintSlice, OuterDigestVariable},
19};
20
21pub mod domain;
22pub mod hints;
23pub mod two_adic_pcs;
24pub mod types;
25pub mod witness;
26
27#[allow(clippy::too_many_arguments)]
33#[allow(unused_variables)]
34pub fn verify_query<C: Config>(
35 builder: &mut Builder<C>,
36 config: &FriConfigVariable<C>,
37 commit_phase_commits: &Array<C, DigestVariable<C>>,
38 index_bits: &Array<C, Var<C::N>>,
39 proof: &FriQueryProofVariable<C>,
40 betas: &Array<C, Ext<C::F, C::EF>>,
41 reduced_openings: &Array<C, Ext<C::F, C::EF>>,
42 log_max_lde_height: RVar<C::N>,
43) -> Ext<C::F, C::EF>
44where
45 C::F: TwoAdicField,
46 C::EF: TwoAdicField,
47{
48 builder.cycle_tracker_start("verify-query");
49 let folded_eval: Ext<C::F, C::EF> = builder.eval(C::F::ZERO);
50 let two_adic_generator_f = config.get_two_adic_generator(builder, log_max_lde_height);
51
52 let two_adic_gen_ext = two_adic_generator_f.to_operand().symbolic();
53 let two_adic_generator_ef: Ext<_, _> = builder.eval(two_adic_gen_ext);
54
55 let index_bits_truncated = index_bits.slice(builder, 0, log_max_lde_height);
56 let x = builder.exp_bits_big_endian(two_adic_generator_ef, &index_bits_truncated);
57
58 builder.assert_usize_eq(
60 proof.commit_phase_openings.len(),
61 commit_phase_commits.len(),
62 );
63 builder
64 .range(0, commit_phase_commits.len())
65 .for_each(|i_vec, builder| {
66 let i = i_vec[0];
67 let log_folded_height = builder.eval_expr(log_max_lde_height - i - C::N::ONE);
68 let log_folded_height_plus_one = builder.eval_expr(log_folded_height + C::N::ONE);
69 let commit = builder.get(commit_phase_commits, i);
70 let step = builder.get(&proof.commit_phase_openings, i);
71 let beta = builder.get(betas, i);
72
73 let reduced_opening = builder.get(reduced_openings, log_folded_height_plus_one);
75 builder.assign(&folded_eval, folded_eval + reduced_opening);
76
77 let index_bit = builder.get(index_bits, i);
78 let index_sibling_mod_2: Var<C::N> =
79 builder.eval(SymbolicVar::from(C::N::ONE) - index_bit);
80 let i_plus_one = builder.eval_expr(i + RVar::one());
81 let index_pair = index_bits.shift(builder, i_plus_one);
82
83 let evals: Array<C, Ext<C::F, C::EF>> = builder.array(2);
84 let eval_0: Ext<C::F, C::EF>;
85 let eval_1: Ext<C::F, C::EF>;
86 if builder.flags.static_only {
87 [eval_0, eval_1] = cond_eval(
88 builder,
89 index_sibling_mod_2,
90 step.sibling_value,
91 folded_eval,
92 );
93 builder.set_value(&evals, 0, eval_0);
94 builder.set_value(&evals, 1, eval_1);
95 } else {
96 builder.set_value(&evals, 0, folded_eval);
97 builder.set_value(&evals, 1, folded_eval);
98 builder.set_value(&evals, index_sibling_mod_2, step.sibling_value);
100 eval_0 = builder.get(&evals, 0);
101 eval_1 = builder.get(&evals, 1);
102 }
103
104 let dims = DimensionsVariable::<C> {
105 log_height: builder.eval(log_folded_height),
106 };
107 let dims_slice: Array<C, DimensionsVariable<C>> = builder.array(1);
108 builder.set_value(&dims_slice, 0, dims);
109
110 let opened_values = builder.array(1);
111 builder.set_value(&opened_values, 0, evals);
112 builder.cycle_tracker_start("verify-batch-ext");
113 verify_batch::<C>(
114 builder,
115 &commit,
116 dims_slice,
117 index_pair,
118 &NestedOpenedValues::Ext(opened_values),
119 &step.opening_proof,
120 );
121 builder.cycle_tracker_end("verify-batch-ext");
122
123 let two_adic_generator_one = config.get_two_adic_generator(builder, Usize::from(1));
124
125 let [xs_0, xs_1]: [Ext<_, _>; 2] =
126 cond_eval(builder, index_sibling_mod_2, x * two_adic_generator_one, x);
127
128 builder.assign(
129 &folded_eval,
130 eval_0 + (beta - xs_0) * (eval_1 - eval_0) / (xs_1 - xs_0),
131 );
132
133 builder.assign(&x, x * x);
134 });
135
136 builder.cycle_tracker_end("verify-query");
137 folded_eval
138}
139
140#[allow(clippy::type_complexity)]
141pub enum NestedOpenedValues<C: Config> {
142 Felt(Array<C, Array<C, Felt<C::F>>>),
143 Ext(Array<C, Array<C, Ext<C::F, C::EF>>>),
144}
145
146#[allow(clippy::type_complexity)]
152#[allow(unused_variables)]
153pub fn verify_batch<C: Config>(
154 builder: &mut Builder<C>,
155 commit: &DigestVariable<C>,
156 dimensions: Array<C, DimensionsVariable<C>>,
157 index_bits: Array<C, Var<C::N>>,
158 opened_values: &NestedOpenedValues<C>,
159 proof: &HintSlice<C>,
160) {
161 if builder.flags.static_only {
162 verify_batch_static(
163 builder,
164 commit,
165 dimensions,
166 index_bits,
167 opened_values,
168 proof,
169 );
170 return;
171 }
172
173 let dimensions = match dimensions {
174 Array::Dyn(ptr, len) => Array::Dyn(ptr, len.clone()),
175 _ => panic!("Expected a dynamic array of felts"),
176 };
177 let commit = match commit {
178 DigestVariable::Felt(arr) => arr,
179 _ => panic!("Expected a dynamic array of felts"),
180 };
181 match opened_values {
182 NestedOpenedValues::Felt(opened_values) => builder.verify_batch_felt(
183 &dimensions,
184 opened_values,
185 proof.id.get_var(),
186 &index_bits,
187 commit,
188 ),
189 NestedOpenedValues::Ext(opened_values) => builder.verify_batch_ext(
190 &dimensions,
191 opened_values,
192 proof.id.get_var(),
193 &index_bits,
194 commit,
195 ),
196 };
197}
198
199#[allow(clippy::type_complexity)]
205#[allow(unused_variables)]
206pub fn verify_batch_static<C: Config>(
207 builder: &mut Builder<C>,
208 commit: &DigestVariable<C>,
209 dimensions: Array<C, DimensionsVariable<C>>,
210 index_bits: Array<C, Var<C::N>>,
211 opened_values: &NestedOpenedValues<C>,
212 proof: &HintSlice<C>,
213) {
214 let commit: OuterDigestVariable<C> = if let DigestVariable::Var(commit) = commit {
215 commit.vec().try_into().unwrap()
216 } else {
217 panic!("Expected a Var commitment");
218 };
219 let index: Usize<C::N> = builder.eval(C::N::ZERO);
221 let mut current_log_height = builder.get(&dimensions, index.clone()).log_height.value();
223 let reducer = opened_values.create_reducer(builder);
225 let mut root = reducer
226 .reduce_fast(
227 builder,
228 index.clone(),
229 &dimensions,
230 current_log_height,
231 opened_values,
232 )
233 .into_outer_digest();
234
235 let witness_refs = builder.get_witness_refs(proof.id.clone()).to_vec();
237 for (i, &witness_ref) in witness_refs.iter().enumerate() {
238 let sibling: OuterDigestVariable<C> = [witness_ref.into()];
239 let bit = builder.get(&index_bits, i);
240
241 let [left, right]: [Var<_>; 2] = cond_eval(builder, bit, root[0], sibling[0]);
242 root = builder.p2_compress([[left], [right]]);
243 current_log_height -= 1;
244
245 builder
246 .if_ne(index.clone(), dimensions.len())
247 .then(|builder| {
248 let next_log_height = builder.get(&dimensions, index.clone()).log_height;
249 builder
250 .if_eq(next_log_height, Usize::from(current_log_height))
251 .then(|builder| {
252 let next_height_openings_digest = reducer
253 .reduce_fast(
254 builder,
255 index.clone(),
256 &dimensions,
257 current_log_height,
258 opened_values,
259 )
260 .into_outer_digest();
261 root = builder.p2_compress([root, next_height_openings_digest]);
262 });
263 })
264 }
265
266 builder.assert_var_eq(root[0], commit[0]);
267}
268
269#[allow(clippy::type_complexity)]
270fn reduce_fast<C: Config, V: MemVariable<C>>(
271 builder: &mut Builder<C>,
272 dim_idx: Usize<C::N>,
273 dims: &Array<C, DimensionsVariable<C>>,
274 cur_log_height: usize,
275 opened_values: &Array<C, Array<C, V>>,
276 nested_opened_values_buffer: &Array<C, Array<C, V>>,
277) -> DigestVariable<C>
278where
279 Array<C, Array<C, V>>: CanPoseidon2Digest<C>,
280{
281 builder.cycle_tracker_start("verify-batch-reduce-fast");
282
283 let nested_opened_values_buffer = if builder.flags.static_only {
286 builder.array(REDUCER_BUFFER_SIZE)
287 } else {
288 let ret = builder.uninit();
290 builder.assign(&ret, nested_opened_values_buffer.clone());
291 ret
292 };
293
294 let nb_opened_values: Usize<_> = builder.eval(C::N::ZERO);
295 let start_dim_idx: Usize<_> = builder.eval(dim_idx.clone());
296 builder.cycle_tracker_start("verify-batch-reduce-fast-setup");
297 let dims_shifted = dims.shift(builder, start_dim_idx.clone());
298 let opened_values_shifted = opened_values.shift(builder, start_dim_idx);
299 iter_zip!(builder, dims_shifted, opened_values_shifted).for_each(|ptr_vec, builder| {
300 let log_height = builder.iter_ptr_get(&dims_shifted, ptr_vec[0]).log_height;
301 builder
302 .if_eq(log_height, Usize::from(cur_log_height))
303 .then(|builder| {
304 let opened_values = builder.iter_ptr_get(&opened_values_shifted, ptr_vec[1]);
305 builder.set_value(
306 &nested_opened_values_buffer,
307 nb_opened_values.clone(),
308 opened_values.clone(),
309 );
310 builder.assign(&nb_opened_values, nb_opened_values.clone() + C::N::ONE);
311 });
312 });
313 builder.assign(&dim_idx, dim_idx.clone() + nb_opened_values.clone());
314 builder.cycle_tracker_end("verify-batch-reduce-fast-setup");
315
316 nested_opened_values_buffer.truncate(builder, nb_opened_values);
317 let h = nested_opened_values_buffer.p2_digest(builder);
318 builder.cycle_tracker_end("verify-batch-reduce-fast");
319 h
320}
321
322struct NestedOpenedValuesReducerVar<C: Config> {
323 buffer: NestedOpenedValues<C>,
324}
325impl<C: Config> NestedOpenedValuesReducerVar<C> {
326 fn reduce_fast(
327 &self,
328 builder: &mut Builder<C>,
329 dim_idx: Usize<C::N>,
330 dims: &Array<C, DimensionsVariable<C>>,
331 cur_log_height: usize,
332 nested_opened_values: &NestedOpenedValues<C>,
333 ) -> DigestVariable<C> {
334 match nested_opened_values {
335 NestedOpenedValues::Felt(opened_values) => {
336 let buffer = match &self.buffer {
337 NestedOpenedValues::Felt(buffer) => buffer,
338 NestedOpenedValues::Ext(_) => unreachable!(),
339 };
340 reduce_fast(
341 builder,
342 dim_idx,
343 dims,
344 cur_log_height,
345 opened_values,
346 buffer,
347 )
348 }
349 NestedOpenedValues::Ext(opened_values) => {
350 let buffer = match &self.buffer {
351 NestedOpenedValues::Felt(_) => unreachable!(),
352 NestedOpenedValues::Ext(buffer) => buffer,
353 };
354 reduce_fast(
355 builder,
356 dim_idx,
357 dims,
358 cur_log_height,
359 opened_values,
360 buffer,
361 )
362 }
363 }
364 }
365}
366
367const REDUCER_BUFFER_SIZE: usize = 8192;
369
370impl<C: Config> NestedOpenedValues<C> {
371 fn create_reducer(&self, builder: &mut Builder<C>) -> NestedOpenedValuesReducerVar<C> {
372 NestedOpenedValuesReducerVar {
373 buffer: match self {
374 NestedOpenedValues::Felt(_) => {
375 NestedOpenedValues::Felt(builder.array(REDUCER_BUFFER_SIZE))
376 }
377 NestedOpenedValues::Ext(_) => {
378 NestedOpenedValues::Ext(builder.array(REDUCER_BUFFER_SIZE))
379 }
380 },
381 }
382 }
383}