openvm_continuations/circuit/inner/verifier/
air.rs

1use std::{array::from_fn, borrow::Borrow};
2
3use openvm_circuit::arch::POSEIDON2_WIDTH;
4use openvm_circuit_primitives::{
5    utils::{and, assert_array_eq, not},
6    ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
7};
8use openvm_recursion_circuit::{
9    bus::{
10        CachedCommitBus, CachedCommitBusMessage, PreHashBus, PreHashMessage, PublicValuesBus,
11        PublicValuesBusMessage,
12    },
13    primitives::bus::{RangeCheckerBus, RangeCheckerBusMessage},
14    utils::assert_zeros,
15};
16use openvm_recursion_circuit_derive::AlignedBorrow;
17use openvm_stark_backend::{
18    interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
19};
20use openvm_verify_stark_host::pvs::{
21    VerifierBasePvs, VerifierDefPvs, CONSTRAINT_EVAL_AIR_ID, CONSTRAINT_EVAL_CACHED_INDEX,
22    LOG_MAX_RECURSION_DEPTH, VERIFIER_PVS_AIR_ID,
23};
24use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, BaseAir};
25use p3_field::{Field, PrimeCharacteristicRing};
26use p3_matrix::Matrix;
27
28use crate::circuit::{
29    inner::bus::{PvsAirConsistencyBus, PvsAirConsistencyMessage},
30    root::NUM_DIGESTS_IN_VM_COMMIT,
31    subair::{HashSliceCtx, HashSliceSubAir},
32    utils::{assert_vk_commit_eq, assert_vk_commit_unset, vk_commit_components},
33};
34
35#[repr(C)]
36#[derive(AlignedBorrow, StructReflection)]
37pub struct VerifierPvsCols<F> {
38    pub proof_idx: F,
39    pub is_valid: F,
40    pub has_verifier_pvs: F,
41
42    pub recursion_flag: F,
43    pub depth_inv: F,
44
45    pub child_pvs: VerifierBasePvs<F>,
46}
47
48#[derive(ColumnsAir)]
49#[columns_via(VerifierPvsCols<u8>)]
50pub struct VerifierPvsAir {
51    pub public_values_bus: PublicValuesBus,
52    pub cached_commit_bus: CachedCommitBus,
53    pub pre_hash_bus: PreHashBus,
54    pub range_bus: RangeCheckerBus,
55    pub pvs_air_consistency_bus: PvsAirConsistencyBus,
56    pub deferral_config: VerifierDeferralConfig,
57}
58
59impl<F> BaseAir<F> for VerifierPvsAir {
60    fn width(&self) -> usize {
61        VerifierPvsCols::<u8>::width() + self.deferral_config.width()
62    }
63}
64impl<F> BaseAirWithPublicValues<F> for VerifierPvsAir {
65    fn num_public_values(&self) -> usize {
66        VerifierBasePvs::<u8>::width() + self.deferral_config.num_public_values()
67    }
68}
69impl<F> PartitionedBaseAir<F> for VerifierPvsAir {}
70
71impl<AB: AirBuilder + InteractionBuilder + AirBuilderWithPublicValues> Air<AB> for VerifierPvsAir {
72    fn eval(&self, builder: &mut AB) {
73        let main = builder.main();
74        let (local, next) = (
75            main.row_slice(0).expect("window should have two elements"),
76            main.row_slice(1).expect("window should have two elements"),
77        );
78
79        let base_cols_width = VerifierPvsCols::<AB::Var>::width();
80        let (base_local, def_local) = local.split_at(base_cols_width);
81        let (base_next, def_next) = next.split_at(base_cols_width);
82
83        let local: &VerifierPvsCols<AB::Var> = (*base_local).borrow();
84        let next: &VerifierPvsCols<AB::Var> = (*base_next).borrow();
85
86        /*
87         * This AIR can optionally handle deferrals, the constraints for which are defined in
88         * function eval_deferrals. We expect vk_commit_cond to be a boolean value that is
89         * true iff local and next's app, leaf, and internal-for-leaf vk commits should be
90         * constrained for equality.
91         */
92        let (vk_commit_cond, deferral_flag, consistency_mult) = match &self.deferral_config {
93            VerifierDeferralConfig::Enabled {
94                hash_slice_subair: subair,
95            } => {
96                let def_local: &VerifierDeferralCols<AB::Var> = (*def_local).borrow();
97                let def_next: &VerifierDeferralCols<AB::Var> = (*def_next).borrow();
98                self.eval_deferrals(builder, local, next, def_local, def_next, subair)
99            }
100            VerifierDeferralConfig::Disabled => {
101                debug_assert_eq!(def_local.len(), 0);
102                (
103                    and(local.is_valid, next.is_valid),
104                    AB::Expr::ZERO,
105                    AB::Expr::ONE,
106                )
107            }
108        };
109
110        /*
111         * Constrain basic features about the non-pvs columns.
112         */
113        builder.assert_bool(local.is_valid);
114        builder.when_first_row().assert_one(local.is_valid);
115        builder
116            .when_transition()
117            .assert_bool(local.is_valid - next.is_valid);
118
119        builder.when_first_row().assert_zero(local.proof_idx);
120        builder
121            .when_transition()
122            .when(and(local.is_valid, next.is_valid))
123            .assert_eq(local.proof_idx + AB::F::ONE, next.proof_idx);
124
125        builder.assert_bool(local.has_verifier_pvs);
126        builder
127            .when(local.has_verifier_pvs)
128            .assert_one(local.is_valid);
129
130        /*
131         * We constrain the consistency of verifier-specific public values. We can determine
132         * what layer a verifier is at using the has_verifier_pvs and internal_flag columns.
133         * There are several cases we cover:
134         *
135         * - has_verifier_pvs == 0: leaf verifier, app (or deferral circuit) children
136         * - has_verifier_pvs == 1 && internal_flag == 0: internal verifier with leaf children
137         * - has_verifier_pvs == 1 && internal_flag == 1: internal_for_leaf children
138         * - has_verifier_pvs == 1 && internal_flag == 2: internal_recursive children
139         *
140         * Note the first (i.e. index 0) internal-recursive layer has recursion_depth 1, the
141         * second has recursion_depth 2, and so on. The recursion_flag column is used to
142         * differentiate between the cases.
143         *
144         * - recursion_flag == 0: non-internal_recursive child
145         * - recursion_flag == 1: internal_recursive child at recursion_depth 1
146         * - recursion_flag == 2: internal_recursive child at recursion_depth 2 or beyond
147         */
148        // constrain the verifier pvs flags and internal_recursive_vk_commit are the same
149        // across all valid rows
150        let both_valid = and(local.is_valid, next.is_valid);
151        let mut when_both_valid = builder.when(both_valid.clone());
152
153        when_both_valid.assert_eq(local.has_verifier_pvs, next.has_verifier_pvs);
154        when_both_valid.assert_eq(local.recursion_flag, next.recursion_flag);
155        when_both_valid.assert_eq(local.child_pvs.internal_flag, next.child_pvs.internal_flag);
156        when_both_valid.assert_eq(
157            local.child_pvs.recursion_depth,
158            next.child_pvs.recursion_depth,
159        );
160
161        assert_vk_commit_eq(
162            &mut when_both_valid,
163            local.child_pvs.internal_recursive_vk_commit,
164            next.child_pvs.internal_recursive_vk_commit,
165        );
166
167        // constrain the other commits are the same when needed
168        let mut when_vk_compare = builder.when(vk_commit_cond);
169
170        assert_vk_commit_eq(
171            &mut when_vk_compare,
172            local.child_pvs.app_vk_commit,
173            next.child_pvs.app_vk_commit,
174        );
175        assert_vk_commit_eq(
176            &mut when_vk_compare,
177            local.child_pvs.leaf_vk_commit,
178            next.child_pvs.leaf_vk_commit,
179        );
180        assert_vk_commit_eq(
181            &mut when_vk_compare,
182            local.child_pvs.internal_for_leaf_vk_commit,
183            next.child_pvs.internal_for_leaf_vk_commit,
184        );
185
186        // constrain that the flags are ternary
187        builder.assert_tern(local.child_pvs.internal_flag);
188        builder.assert_tern(local.recursion_flag);
189
190        // constrain that recursion_flag is 0 iff recursion_depth == 0, recursion_flag == 1
191        // if recursion_depth == 1, and recursion_flag == 2 iff recursion_depth >= 2
192        let half = AB::F::TWO.inverse();
193        let is_recursion_flag_two =
194            (local.recursion_flag - AB::F::ONE) * local.recursion_flag * half;
195
196        builder
197            .when_ne(local.recursion_flag, AB::F::TWO)
198            .assert_eq(local.child_pvs.recursion_depth, local.recursion_flag);
199        builder
200            .when_ne(local.child_pvs.recursion_depth, AB::F::ZERO)
201            .when_ne(local.child_pvs.recursion_depth, AB::F::ONE)
202            .assert_eq(local.recursion_flag, AB::F::TWO);
203        builder.assert_eq(
204            is_recursion_flag_two.clone(),
205            local.child_pvs.recursion_depth
206                * (local.child_pvs.recursion_depth - AB::F::ONE)
207                * local.depth_inv,
208        );
209
210        // constrain recursion_depth is 0 when internal_flag is 0 or 1
211        builder
212            .when_ne(local.child_pvs.internal_flag, AB::F::TWO)
213            .assert_zero(local.child_pvs.recursion_depth);
214
215        // range check child recursion_depth to be in [0, MAX_RECURSION_DEPTH)
216        self.range_bus.lookup_key(
217            builder,
218            RangeCheckerBusMessage {
219                value: local.child_pvs.recursion_depth.into(),
220                max_bits: AB::Expr::from_u8(LOG_MAX_RECURSION_DEPTH),
221            },
222            local.is_valid,
223        );
224
225        // constrain that internal_flag is 2 when recursion_flag is set, and not 2 otherwise
226        builder
227            .when(local.recursion_flag)
228            .assert_eq(local.child_pvs.internal_flag, AB::F::TWO);
229        builder
230            .when_ne(local.recursion_flag, AB::F::ONE)
231            .when_ne(local.recursion_flag, AB::F::TWO)
232            .assert_bool(local.child_pvs.internal_flag);
233
234        // constrain that child commits are 0 when they shouldn't be defined
235        let is_leaf = not(local.has_verifier_pvs);
236        let is_internal = local.has_verifier_pvs;
237
238        builder
239            .when(is_leaf.clone())
240            .assert_zero(local.child_pvs.internal_flag);
241
242        assert_vk_commit_unset(
243            &mut builder.when(is_leaf.clone()),
244            local.child_pvs.app_vk_commit,
245        );
246        assert_vk_commit_unset(
247            &mut builder.when(
248                (local.child_pvs.internal_flag - AB::F::ONE)
249                    * (local.child_pvs.internal_flag - AB::F::TWO),
250            ),
251            local.child_pvs.leaf_vk_commit,
252        );
253        assert_vk_commit_unset(
254            &mut builder.when(local.child_pvs.internal_flag - AB::F::TWO),
255            local.child_pvs.internal_for_leaf_vk_commit,
256        );
257        assert_vk_commit_unset(
258            &mut builder.when(local.recursion_flag - AB::F::TWO),
259            local.child_pvs.internal_recursive_vk_commit,
260        );
261
262        /*
263         * We need to receive public values from ProofShapeModule to ensure the values being read
264         * here are correct. This AIR will only read values if it's internal.
265         */
266        let verifier_pvs_id = AB::Expr::from_usize(VERIFIER_PVS_AIR_ID);
267
268        for (pv_idx, value) in local.child_pvs.as_slice().iter().enumerate() {
269            self.public_values_bus.receive(
270                builder,
271                local.proof_idx,
272                PublicValuesBusMessage {
273                    air_idx: verifier_pvs_id.clone(),
274                    pv_idx: AB::Expr::from_usize(pv_idx),
275                    value: (*value).into(),
276                },
277                local.is_valid * is_internal,
278            );
279        }
280
281        /*
282         * We also need to receive cached commits from ProofShapeModule. Note that the
283         * app/deferral circuit cached commits are received in another AIR, so only the
284         * internal verifier will receive them here.
285         */
286        let is_internal_flag_zero = (local.child_pvs.internal_flag - AB::F::ONE)
287            * (local.child_pvs.internal_flag - AB::F::TWO)
288            * half;
289        let is_internal_flag_one =
290            (AB::Expr::TWO - local.child_pvs.internal_flag) * local.child_pvs.internal_flag;
291        let is_recursion_flag_one = (AB::Expr::TWO - local.recursion_flag) * local.recursion_flag;
292        let cached_commit = from_fn(|i| {
293            is_internal_flag_zero.clone() * local.child_pvs.app_vk_commit.cached_commit[i]
294                + is_internal_flag_one.clone() * local.child_pvs.leaf_vk_commit.cached_commit[i]
295                + is_recursion_flag_one.clone()
296                    * local.child_pvs.internal_for_leaf_vk_commit.cached_commit[i]
297                + is_recursion_flag_two.clone()
298                    * local.child_pvs.internal_recursive_vk_commit.cached_commit[i]
299        });
300
301        self.cached_commit_bus.receive(
302            builder,
303            local.proof_idx,
304            CachedCommitBusMessage {
305                air_idx: AB::Expr::from_usize(CONSTRAINT_EVAL_AIR_ID),
306                cached_idx: AB::Expr::from_usize(CONSTRAINT_EVAL_CACHED_INDEX),
307                global_cached_idx: AB::Expr::ZERO,
308                cached_commit,
309            },
310            local.is_valid * is_internal,
311        );
312
313        /*
314         * We provide proof metadata for lookup here to ensure consistency between AIRs that
315         * process public values.
316         */
317        self.pvs_air_consistency_bus.add_key_with_lookups(
318            builder,
319            local.proof_idx,
320            PvsAirConsistencyMessage {
321                deferral_flag,
322                has_verifier_pvs: local.has_verifier_pvs.into(),
323            },
324            local.is_valid * consistency_mult,
325        );
326
327        /*
328         * Finally, we need to constrain that the public values this AIR produces are consistent
329         * with the child's. Note that we only impose constraints for layers below the current
330         * one - it is impossible for the current layer to know its own commit, and future layers
331         * will catch if we preemptively define a current or future verifier commit.
332         */
333        let base_pvs_width = VerifierBasePvs::<AB::Var>::width();
334        let &VerifierBasePvs::<_> {
335            internal_flag,
336            app_vk_commit,
337            leaf_vk_commit,
338            internal_for_leaf_vk_commit,
339            recursion_depth,
340            internal_recursive_vk_commit,
341        } = builder.public_values()[0..base_pvs_width].borrow();
342
343        // constrain internal_flag is 0 at the leaf level
344        builder
345            .when(and(local.is_valid, is_leaf.clone()))
346            .assert_zero(internal_flag);
347
348        // constrain recursion_depth is 0 at the leaf and internal_for_leaf levels
349        builder
350            .when(
351                local.is_valid
352                    * (local.child_pvs.internal_flag - AB::F::ONE)
353                    * (local.child_pvs.internal_flag - AB::F::TWO),
354            )
355            .assert_zero(recursion_depth);
356
357        // constraint internal_flag is incremented properly at internal levels
358        builder
359            .when(is_internal)
360            .when_ne(local.child_pvs.internal_flag, AB::F::TWO)
361            .assert_eq(internal_flag, local.child_pvs.internal_flag + AB::F::ONE);
362
363        // constrain app_vk_commit is set at all internal levels and matches the first row
364        assert_vk_commit_eq(
365            &mut builder.when_first_row().when(is_internal),
366            local.child_pvs.app_vk_commit,
367            app_vk_commit,
368        );
369
370        // constrain verifier-specific pvs at all internal_recursive levels
371        builder
372            .when(local.child_pvs.internal_flag)
373            .assert_zero(internal_flag.into() - AB::F::TWO);
374        assert_vk_commit_eq(
375            &mut builder.when_first_row().when(local.child_pvs.internal_flag),
376            local.child_pvs.leaf_vk_commit,
377            leaf_vk_commit,
378        );
379
380        // constrain recursion_depth increments at each internal_recursive level
381        builder
382            .when(local.child_pvs.internal_flag)
383            .assert_one(recursion_depth.into() - local.child_pvs.recursion_depth);
384
385        // constrain internal_for_leaf_vk_commit is set at internal_recursive levels after
386        // the first and matches the first row
387        assert_vk_commit_eq(
388            &mut builder.when_first_row().when(local.recursion_flag),
389            local.child_pvs.internal_for_leaf_vk_commit,
390            internal_for_leaf_vk_commit,
391        );
392
393        // constrain internal_recursive_vk_commit is set at internal_recursive levels after
394        // the first and matches the first row
395        assert_vk_commit_eq(
396            &mut builder.when(local.recursion_flag * (local.recursion_flag - AB::F::ONE)),
397            local.child_pvs.internal_recursive_vk_commit,
398            internal_recursive_vk_commit,
399        );
400
401        /*
402         * Unlike the cached commits, we receive the pre-hash on the same layer that it's
403         * observed. We receive it from ProofShapeModule also.
404         *
405         * By the constraints above, the pvs recursion_depth is 1 iff the child internal_flag
406         * is 1, and is at least 2 iff the child internal_flag is 2.
407         */
408        let internal_flag = internal_flag.into();
409
410        let is_pvs_internal_flag_zero = (internal_flag.clone() - AB::Expr::ONE)
411            * (internal_flag.clone() - AB::Expr::TWO)
412            * half;
413        let is_pvs_internal_flag_one =
414            (AB::Expr::TWO - internal_flag.clone()) * internal_flag.clone();
415        let is_internal_flag_two =
416            (local.child_pvs.internal_flag - AB::Expr::ONE) * local.child_pvs.internal_flag * half;
417
418        let vk_pre_hash = from_fn(|i| {
419            is_pvs_internal_flag_zero.clone() * app_vk_commit.vk_pre_hash[i].into()
420                + is_pvs_internal_flag_one.clone() * leaf_vk_commit.vk_pre_hash[i].into()
421                + is_internal_flag_one.clone() * internal_for_leaf_vk_commit.vk_pre_hash[i].into()
422                + is_internal_flag_two.clone() * internal_recursive_vk_commit.vk_pre_hash[i].into()
423        });
424
425        self.pre_hash_bus.receive(
426            builder,
427            local.proof_idx,
428            PreHashMessage { vk_pre_hash },
429            local.is_valid,
430        );
431    }
432}
433
434///////////////////////////////////////////////////////////////////////////////
435// DEFERRAL SUPPORT
436///////////////////////////////////////////////////////////////////////////////
437
438pub enum VerifierDeferralConfig {
439    Enabled { hash_slice_subair: HashSliceSubAir },
440    Disabled,
441}
442
443impl VerifierDeferralConfig {
444    pub fn width(&self) -> usize {
445        match self {
446            VerifierDeferralConfig::Enabled { .. } => VerifierDeferralCols::<u8>::width(),
447            VerifierDeferralConfig::Disabled => 0,
448        }
449    }
450
451    pub fn num_public_values(&self) -> usize {
452        match self {
453            VerifierDeferralConfig::Enabled { .. } => VerifierDefPvs::<u8>::width(),
454            VerifierDeferralConfig::Disabled => 0,
455        }
456    }
457}
458
459#[repr(C)]
460#[derive(AlignedBorrow)]
461pub struct VerifierDeferralCols<F> {
462    pub is_last: F,
463    pub intermediate_states: [[F; POSEIDON2_WIDTH]; NUM_DIGESTS_IN_VM_COMMIT - 1],
464    pub child_pvs: VerifierDefPvs<F>,
465}
466
467#[repr(C)]
468#[derive(AlignedBorrow)]
469pub struct VerifierCombinedPvs<F> {
470    pub base: VerifierBasePvs<F>,
471    pub def: VerifierDefPvs<F>,
472}
473
474impl VerifierPvsAir {
475    fn eval_deferrals<AB>(
476        &self,
477        builder: &mut AB,
478        base_local: &VerifierPvsCols<AB::Var>,
479        base_next: &VerifierPvsCols<AB::Var>,
480        def_local: &VerifierDeferralCols<AB::Var>,
481        def_next: &VerifierDeferralCols<AB::Var>,
482        hash_slice_subair: &HashSliceSubAir,
483    ) -> (AB::Expr, AB::Expr, AB::Expr)
484    where
485        AB: AirBuilder + InteractionBuilder + AirBuilderWithPublicValues,
486    {
487        /*
488         * The deferral_flag should be 0 if a proof has only VM public values defined, 1 if
489         * only deferral public values, and 2 if both. There are 4 valid cases:
490         * - All valid rows have deferral_flag == 0
491         * - All valid rows have deferral_flag == 1
492         * - There are exactly two rows with deferral_flag == row_idx
493         * - There is exactly one row with deferral_flag == 2
494         */
495        let delta = def_next.child_pvs.deferral_flag - def_local.child_pvs.deferral_flag;
496        builder.assert_tern(def_local.child_pvs.deferral_flag);
497
498        // constrain that is_last is correctly set on the last valid row
499        builder.assert_bool(def_local.is_last);
500        builder
501            .when(def_local.is_last)
502            .assert_one(base_local.is_valid);
503        builder
504            .when(and(base_local.is_valid, not(def_local.is_last)))
505            .assert_one(base_next.is_valid);
506        builder
507            .when(def_local.is_last)
508            .assert_zero(base_next.is_valid * base_next.proof_idx);
509        builder
510            .when_last_row()
511            .when(base_local.is_valid)
512            .assert_one(def_local.is_last);
513
514        // constrain that delta is 0 or 1
515        builder.when_transition().assert_bool(delta.clone());
516
517        // constrain that if deferral_flag is 1 or 2, it cannot change later (note that if
518        // deferral_flag is 1 or 2 there may only be 1 or 2 rows)
519        builder
520            .when_transition()
521            .when(def_local.child_pvs.deferral_flag)
522            .assert_zero(delta.clone());
523
524        // constrain that the 0->1 transition happens only on the first row
525        builder
526            .when_transition()
527            .when(base_local.proof_idx)
528            .assert_zero(delta.clone());
529
530        // constrain that if first row is 2, it must be the only valid row
531        builder
532            .when(def_local.child_pvs.deferral_flag)
533            .when_ne(def_local.child_pvs.deferral_flag, AB::F::ONE)
534            .assert_one(def_local.is_last);
535
536        // constrain row 1 to be the last on the 0->1 transition
537        builder
538            .when_transition()
539            .when(delta.clone())
540            .assert_one(def_next.is_last);
541
542        /*
543         * We also need to constrain the deferral-related public values. In particular, the
544         * def_hook_commit should be defined exactly when internal_for_leaf_vk_commit
545         * is for deferral_flag == 1.
546         */
547        // constrain that delta == 1 only at some internal_recursive layer
548        builder
549            .when(delta.clone())
550            .assert_eq(base_local.child_pvs.internal_flag, AB::F::TWO);
551        builder
552            .when(def_local.child_pvs.deferral_flag)
553            .when_ne(def_local.child_pvs.deferral_flag, AB::F::ONE)
554            .assert_eq(base_local.child_pvs.internal_flag, AB::F::TWO);
555
556        // constrain that def_hook_commit is unset when internal_flag < 2
557        assert_zeros(
558            &mut builder.when(base_local.child_pvs.internal_flag - AB::F::TWO),
559            def_local.child_pvs.def_hook_commit,
560        );
561
562        /*
563         * We need to receive deferral-specific public values from ProofShapeModule to
564         * ensure the values being read are correct.
565         */
566        let verifier_pvs_id = AB::Expr::from_usize(VERIFIER_PVS_AIR_ID);
567        let pvs_offset = VerifierBasePvs::<AB::Var>::width();
568
569        for (pv_idx, value) in def_local.child_pvs.as_slice().iter().enumerate() {
570            self.public_values_bus.receive(
571                builder,
572                base_local.proof_idx,
573                PublicValuesBusMessage {
574                    air_idx: verifier_pvs_id.clone(),
575                    pv_idx: AB::Expr::from_usize(pv_idx + pvs_offset),
576                    value: (*value).into(),
577                },
578                base_local.is_valid * base_local.has_verifier_pvs,
579            );
580        }
581
582        /*
583         * Finally, we need to constrain that the deferral-specific public values this AIR
584         * produces are consistent with the child's.
585         */
586        let &VerifierCombinedPvs::<_> {
587            base: base_pvs,
588            def: def_pvs,
589        } = builder.public_values().borrow();
590
591        let base_pvs_ref: &VerifierBasePvs<_> = base_pvs.as_slice().borrow();
592        let &VerifierBasePvs::<_> { internal_flag, .. } = base_pvs_ref;
593
594        let &VerifierDefPvs::<_> {
595            deferral_flag,
596            def_hook_commit,
597        } = def_pvs.as_slice().borrow();
598
599        // constrain deferral_flag either matches each row, or is 2 when delta is non-zero
600        builder
601            .when(delta.clone())
602            .assert_eq(deferral_flag, AB::F::TWO);
603        builder
604            .when_ne(delta.clone(), AB::F::ONE)
605            .when_ne(delta.clone(), -AB::F::ONE)
606            .assert_eq(deferral_flag, def_local.child_pvs.deferral_flag);
607
608        // constrain def_hook_commit matches if set in child_pvs
609        assert_array_eq(
610            &mut builder
611                .when(base_local.recursion_flag)
612                .when(def_local.child_pvs.deferral_flag),
613            def_local.child_pvs.def_hook_commit,
614            def_hook_commit,
615        );
616
617        // constrain the child def_hook_commit is defined when internal_flag is 2 and
618        // deferral_flag is non-zero
619        let is_child_def_hook_vk_defined = base_local.child_pvs.internal_flag
620            * (base_local.child_pvs.internal_flag - AB::Expr::ONE)
621            * def_local.child_pvs.deferral_flag;
622
623        assert_array_eq(
624            &mut builder.when(is_child_def_hook_vk_defined),
625            def_local.child_pvs.def_hook_commit,
626            def_hook_commit,
627        );
628
629        // constrain def_hook_commit = hash_slice(vk_commit_components) when
630        // internal_flag is 2 and deferral_flag is 1
631        let compute_def_hook_commit = internal_flag.into()
632            * (internal_flag.into() - AB::Expr::ONE)
633            * deferral_flag.into()
634            * (AB::Expr::TWO - deferral_flag.into())
635            * AB::F::TWO.inverse();
636
637        hash_slice_subair.eval(
638            builder,
639            HashSliceCtx {
640                elements: &vk_commit_components(base_pvs_ref)
641                    .into_iter()
642                    .map(|c| c.map(Into::into))
643                    .collect::<Vec<_>>(),
644                intermediate: def_local
645                    .intermediate_states
646                    .map(|v| v.map(Into::into))
647                    .as_slice(),
648                result: &def_hook_commit.map(Into::into),
649                enabled: &compute_def_hook_commit,
650            },
651        );
652
653        /*
654         * Finally, we need to generate some expressions for use in the outer constraints.
655         * vk_commit_cond is non-zero iff on a transition row and all deferral flags are
656         * the same, and consistency_mult is the number of lookups this AIR will receive
657         * on the PvsAirConsistencyBus.
658         */
659        let vk_commit_cond =
660            and(base_local.is_valid, not(def_local.is_last)) * (AB::Expr::ONE - delta);
661        let deferral_flag = def_local.child_pvs.deferral_flag.into();
662        let consistency_mult = base_local.has_verifier_pvs + AB::Expr::ONE;
663
664        (vk_commit_cond, deferral_flag, consistency_mult)
665    }
666}